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,2340 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../cohort/tpt'
4
+
5
+ module MalawiHivProgramReports
6
+ module Moh
7
+ # This is the Cohort Builder class
8
+ # rubocop:disable Metrics/ClassLength
9
+ class CohortBuilder
10
+ QUARTER_LENGTH = 3.months
11
+
12
+ include MalawiHivProgramReports::Utils::ModelUtils
13
+ include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
14
+ include MalawiHivProgramReports::Adapters::Moh::Custom
15
+
16
+ def initialize(outcomes_definition: 'moh', **kwargs)
17
+ unless %w[moh pepfar].include?(outcomes_definition.downcase)
18
+ raise ::ArgumentError, "Invalid outcomes_definition `#{outcomes_definition}` expected moh or pepfar"
19
+ end
20
+
21
+ @location = kwargs[:location]
22
+ @adapter = ActiveRecord::Base.connection.adapter_name.downcase
23
+ @outcomes_definition = outcomes_definition
24
+ end
25
+
26
+ def init_temporary_tables(_start_date, end_date, occupation)
27
+ create_temp_cohort_members_table
28
+ create_tmp_patient_table
29
+ drop_temp_register_start_date_table
30
+ drop_temp_other_patient_types
31
+ drop_temp_order_details
32
+ create_temp_other_patient_types(end_date)
33
+ create_temp_register_start_date_table(end_date)
34
+ create_temp_order_details(end_date)
35
+ load_data_into_temp_earliest_start_date(end_date.to_date, occupation)
36
+ update_cum_outcome(end_date)
37
+ end
38
+
39
+ def build(cohort_struct, start_date, end_date, occupation)
40
+ # load_tmp_patient_table(cohort_struct)
41
+ create_temp_cohort_members_table
42
+ create_tmp_patient_table
43
+ drop_temp_register_start_date_table
44
+ drop_temp_other_patient_types
45
+ drop_temp_order_details
46
+ create_temp_other_patient_types(end_date)
47
+ create_temp_register_start_date_table(end_date)
48
+ create_temp_order_details(end_date)
49
+ load_data_into_temp_earliest_start_date(end_date.to_date, occupation)
50
+
51
+ # create_tmp_patient_table_2(end_date)
52
+
53
+ time_started = Time.now.strftime('%Y-%m-%d %H:%M:%S')
54
+
55
+ # create_temp_earliest_start_date_table(end_date)
56
+ quarter_start_date = start_date.to_date
57
+
58
+ # Get earliest date enrolled
59
+ cum_start_date = get_cum_start_date
60
+
61
+ cum_start_date = start_date if cum_start_date.blank?
62
+
63
+ # Total registeres
64
+ cohort_struct.total_registered = total_registered(start_date, end_date)
65
+ cohort_struct.cum_total_registered = total_registered(cum_start_date, end_date)
66
+ cohort_struct.quarterly_total_registered = total_registered(quarter_start_date, end_date)
67
+
68
+ # Patients initiated on ART first time
69
+ cohort_struct.initiated_on_art_first_time = initiated_on_art_first_time(start_date, end_date)
70
+ cohort_struct.cum_initiated_on_art_first_time = initiated_on_art_first_time(cum_start_date, end_date)
71
+ cohort_struct.quarterly_initiated_on_art_first_time = initiated_on_art_first_time(quarter_start_date, end_date)
72
+
73
+ cohort_struct.males_initiated_on_art_first_time = males_initiated_on_art_first_time(start_date, end_date,
74
+ cohort_struct.initiated_on_art_first_time)
75
+ cohort_struct.cum_males_initiated_on_art_first_time = males_initiated_on_art_first_time(cum_start_date,
76
+ end_date, cohort_struct.cum_initiated_on_art_first_time)
77
+
78
+ # Patients re-initiated on ART
79
+ cohort_struct.re_initiated_on_art = re_initiated_on_art(start_date, end_date)
80
+ cohort_struct.cum_re_initiated_on_art = re_initiated_on_art(cum_start_date, end_date)
81
+ cohort_struct.quarterly_re_initiated_on_art = re_initiated_on_art(quarter_start_date, end_date)
82
+
83
+ # Patients transferred in on ART
84
+ cohort_struct.transfer_in = transfer_in(start_date, end_date, cohort_struct.re_initiated_on_art)
85
+ cohort_struct.cum_transfer_in = transfer_in(cum_start_date, end_date, cohort_struct.cum_re_initiated_on_art)
86
+ cohort_struct.quarterly_transfer_in = transfer_in(quarter_start_date, end_date,
87
+ cohort_struct.quarterly_re_initiated_on_art)
88
+
89
+ # All males
90
+ cohort_struct.all_males = males(start_date, end_date)
91
+ cohort_struct.cum_all_males = males(cum_start_date, end_date)
92
+ cohort_struct.quarterly_all_males = males(quarter_start_date, end_date)
93
+
94
+ # Pregnant females (all ages)
95
+ cohort_struct.pregnant_females_all_ages = pregnant_females_all_ages(start_date, end_date)
96
+ cohort_struct.cum_pregnant_females_all_ages = pregnant_females_all_ages(cum_start_date, end_date)
97
+ cohort_struct.quarterly_pregnant_females_all_ages = pregnant_females_all_ages(quarter_start_date, end_date)
98
+
99
+ cohort_struct.initial_pregnant_females_all_ages = initial_females_all_ages(start_date, end_date,
100
+ cohort_struct.pregnant_females_all_ages)
101
+ cohort_struct.cum_initial_pregnant_females_all_ages = initial_females_all_ages(cum_start_date, end_date,
102
+ cohort_struct.cum_pregnant_females_all_ages)
103
+
104
+ # Non-pregnant females (all ages)
105
+ # Unique PatientProgram entries at the current location for those patients with at least one state ON ARVs
106
+ # and earliest start date of the 'ON ARVs' state within the quarter and having gender of
107
+ # related PERSON entry as F for female and no entries of 'IS PATIENT PREGNANT?' observation answered 'YES'
108
+ # in related HIV CLINIC CONSULTATION encounters not within 28 days from earliest registration date
109
+ cohort_struct.non_pregnant_females = non_pregnant_females(start_date, end_date,
110
+ cohort_struct.pregnant_females_all_ages)
111
+ cohort_struct.cum_non_pregnant_females = non_pregnant_females(cum_start_date, end_date,
112
+ cohort_struct.cum_pregnant_females_all_ages)
113
+ cohort_struct.quarterly_non_pregnant_females = non_pregnant_females(quarter_start_date, end_date,
114
+ cohort_struct.cum_pregnant_females_all_ages)
115
+
116
+ cohort_struct.initial_non_pregnant_females_all_ages = initial_females_all_ages(start_date, end_date, cohort_struct.non_pregnant_females.map do |a|
117
+ a['patient_id']
118
+ end)
119
+ cohort_struct.cum_initial_non_pregnant_females_all_ages = initial_females_all_ages(cum_start_date, end_date, cohort_struct.cum_non_pregnant_females.map do |a|
120
+ a['patient_id']
121
+ end)
122
+
123
+ # Children below 24 months at ART initiation
124
+ cohort_struct.children_below_24_months_at_art_initiation = children_below_24_months_at_art_initiation(
125
+ start_date, end_date
126
+ )
127
+ cohort_struct.cum_children_below_24_months_at_art_initiation = children_below_24_months_at_art_initiation(
128
+ cum_start_date, end_date
129
+ )
130
+ cohort_struct.quarterly_children_below_24_months_at_art_initiation = children_below_24_months_at_art_initiation(
131
+ quarter_start_date, end_date
132
+ )
133
+
134
+ # Children 24 months – 14 years at ART initiation
135
+ cohort_struct.children_24_months_14_years_at_art_initiation = children_24_months_14_years_at_art_initiation(
136
+ start_date, end_date
137
+ )
138
+ cohort_struct.cum_children_24_months_14_years_at_art_initiation = children_24_months_14_years_at_art_initiation(
139
+ cum_start_date, end_date
140
+ )
141
+ cohort_struct.quarterly_children_24_months_14_years_at_art_initiation = children_24_months_14_years_at_art_initiation(
142
+ quarter_start_date, end_date
143
+ )
144
+
145
+ # Adults at ART initiation
146
+ cohort_struct.adults_at_art_initiation = adults_at_art_initiation(start_date, end_date)
147
+ cohort_struct.cum_adults_at_art_initiation = adults_at_art_initiation(cum_start_date, end_date)
148
+ cohort_struct.quarterly_adults_at_art_initiation = adults_at_art_initiation(quarter_start_date, end_date)
149
+
150
+ # Unknown age
151
+ cohort_struct.unknown_age = unknown_age(start_date, end_date)
152
+ cohort_struct.cum_unknown_age = unknown_age(cum_start_date, end_date)
153
+ cohort_struct.quarterly_unknown_age = unknown_age(quarter_start_date, end_date)
154
+
155
+ # Unknown gender
156
+ cohort_struct.unknown_gender = unknown_gender(start_date, end_date)
157
+ cohort_struct.cum_unknown_gender = unknown_gender(cum_start_date, end_date)
158
+
159
+ # Unique PatientProgram entries at the current location for those
160
+ # patients with at least one state ON ARVs and earliest start date
161
+ # of the 'ON ARVs' state within the quarter and having a
162
+ # REASON FOR ELIGIBILITY observation with an answer as PRESUMED SEVERE HIV
163
+ cohort_struct.presumed_severe_hiv_disease_in_infants = presumed_severe_hiv_disease_in_infants(start_date,
164
+ end_date)
165
+ cohort_struct.cum_presumed_severe_hiv_disease_in_infants = presumed_severe_hiv_disease_in_infants(
166
+ cum_start_date, end_date
167
+ )
168
+ cohort_struct.quarterly_presumed_severe_hiv_disease_in_infants = presumed_severe_hiv_disease_in_infants(
169
+ quarter_start_date, end_date
170
+ )
171
+
172
+ # Confirmed HIV infection in infants (PCR)
173
+
174
+ # Unique PatientProgram entries at the current location for those patients with at least one state ON ARVs
175
+ # and earliest start date of the 'ON ARVs' state within the quarter and
176
+ # having a REASON FOR ELIGIBILITY observation with an answer as HIV PCR
177
+ cohort_struct.confirmed_hiv_infection_in_infants_pcr = confirmed_hiv_infection_in_infants_pcr(start_date,
178
+ end_date)
179
+ cohort_struct.cum_confirmed_hiv_infection_in_infants_pcr = confirmed_hiv_infection_in_infants_pcr(
180
+ cum_start_date, end_date
181
+ )
182
+ cohort_struct.quarterly_confirmed_hiv_infection_in_infants_pcr = confirmed_hiv_infection_in_infants_pcr(
183
+ quarter_start_date, end_date
184
+ )
185
+
186
+ # WHO stage 1 or 2, CD4 below threshold
187
+ # Unique PatientProgram entries at the current location for those patients with at least one state ON ARVs
188
+ # and earliest start date of the 'ON ARVs' state within the quarter and having a REASON FOR ELIGIBILITY
189
+ # observation with an answer as CD4 COUNT LESS THAN OR EQUAL TO 350 or CD4 COUNT LESS THAN OR EQUAL TO 750
190
+ cohort_struct.who_stage_two = who_stage_two(start_date, end_date)
191
+ cohort_struct.cum_who_stage_two = who_stage_two(cum_start_date, end_date)
192
+ cohort_struct.quarterly_who_stage_two = who_stage_two(quarter_start_date, end_date)
193
+
194
+ # Breastfeeding mothers
195
+
196
+ # Unique PatientProgram entries at the current location for those patients with at least one state
197
+ # ON ARVs and earliest start date of the 'ON ARVs' state within the quarter
198
+ # and having a REASON FOR ELIGIBILITY observation with an answer as BREASTFEEDING
199
+ cohort_struct.breastfeeding_mothers = breastfeeding_mothers(start_date, end_date)
200
+ cohort_struct.cum_breastfeeding_mothers = breastfeeding_mothers(cum_start_date, end_date)
201
+ cohort_struct.quarterly_breastfeeding_mothers = breastfeeding_mothers(quarter_start_date, end_date)
202
+
203
+ # Pregnant women
204
+
205
+ # Unique PatientProgram entries at the current location for those patients with at least one state ON ARVs
206
+ # and earliest start date of the 'ON ARVs' state within the quarter
207
+ # and having a REASON FOR ELIGIBILITY observation with an answer as PATIENT PREGNANT
208
+ cohort_struct.pregnant_women = pregnant_women(start_date, end_date)
209
+ cohort_struct.cum_pregnant_women = pregnant_women(cum_start_date, end_date)
210
+ cohort_struct.quarterly_pregnant_women = pregnant_women(quarter_start_date, end_date)
211
+
212
+ # WHO STAGE 3
213
+ # Unique PatientProgram entries at the current location for those patients with at least
214
+ # one state ON ARVs and earliest start date of the 'ON ARVs' state within the quarter
215
+ # and having a REASON FOR ELIGIBILITY observation with an answer as WHO STAGE III
216
+ cohort_struct.who_stage_three = who_stage_three(start_date, end_date)
217
+ cohort_struct.cum_who_stage_three = who_stage_three(cum_start_date, end_date)
218
+ cohort_struct.quarterly_who_stage_three = who_stage_three(quarter_start_date, end_date)
219
+
220
+ # WHO STAGE 4
221
+ # Unique PatientProgram entries at the current location for those patients with at least
222
+ # one state ON ARVs and earliest start date of the 'ON ARVs' state within the quarter
223
+ # and having a REASON FOR ELIGIBILITY observation with an answer as WHO STAGE IV
224
+ cohort_struct.who_stage_four = who_stage_four(start_date, end_date)
225
+ cohort_struct.cum_who_stage_four = who_stage_four(cum_start_date, end_date)
226
+ cohort_struct.quarterly_who_stage_four = who_stage_four(quarter_start_date, end_date)
227
+
228
+ # Asymptomatic
229
+ # Unique PatientProgram entries at the current location for those patients with at least
230
+ # one state ON ARVs and earliest start date of the 'ON ARVs' state within the quarter
231
+ # and having a REASON FOR ELIGIBILITY observation with an answer as Lymphocytes
232
+ # or LYMPHOCYTE COUNT BELOW THRESHOLD WITH WHO STAGE 2
233
+
234
+ # For all those patients with WHO stage 1 and 2, only those that were enrolled
235
+ # after or on 2016-04-01 revised_guidelines_start_date = "2016-04-01"
236
+ cohort_struct.asymptomatic = asymptomatic(start_date, end_date)
237
+ cohort_struct.cum_asymptomatic = asymptomatic(cum_start_date, end_date)
238
+ cohort_struct.quarterly_asymptomatic = asymptomatic(quarter_start_date, end_date)
239
+
240
+ # Unknown / other reason outside guidelines
241
+ # Unique PatientProgram entries at the current location for those patients with at least one state ON ARVs
242
+ # and earliest start date of the 'ON ARVs' state within the quarter
243
+ # and having a REASON FOR ELIGIBILITY observation with an answer as UNKNOWN
244
+ cohort_struct.unknown_other_reason_outside_guidelines = unknown_other_reason_outside_guidelines(start_date,
245
+ end_date)
246
+ cohort_struct.cum_unknown_other_reason_outside_guidelines = unknown_other_reason_outside_guidelines(
247
+ cum_start_date, end_date
248
+ )
249
+ cohort_struct.quarterly_unknown_other_reason_outside_guidelines = unknown_other_reason_outside_guidelines(
250
+ quarter_start_date, end_date
251
+ )
252
+
253
+ # Children 12-23 months
254
+
255
+ # Unique PatientProgram entries at the current location for those patients with at least one state
256
+ # ON ARVs and earliest start date of the 'ON ARVs' state within the quarter and having
257
+ # Confirmed HIV Infection (HIV Rapid antibody test or DNA-PCR), regardless of WHO stage and CD4 Count
258
+ cohort_struct.children_12_59_months = children_12_59_months(start_date, end_date)
259
+ cohort_struct.cum_children_12_59_months = children_12_59_months(cum_start_date, end_date)
260
+ cohort_struct.quarterly_children_12_59_months = children_12_59_months(quarter_start_date, end_date)
261
+
262
+ # Current EPISODE OF TB
263
+
264
+ # Unique PatientProgram entries at the current location for those patients with at least one state
265
+ # ON ARVs and earliest start date of the 'ON ARVs' state within the quarter and having a
266
+ # CURRENT EPISODE OF TB observation at the HIV staging encounter on the initiation date
267
+ cohort_struct.current_episode_of_tb = current_episode_of_tb(start_date, end_date)
268
+ cohort_struct.cum_current_episode_of_tb = current_episode_of_tb(cum_start_date, end_date)
269
+ cohort_struct.quarterly_current_episode_of_tb = current_episode_of_tb(quarter_start_date, end_date)
270
+
271
+ # TB within the last 2 years
272
+
273
+ # Unique PatientProgram entries at the current location for those patients with at least one state ON ARVs
274
+ # and earliest start date of the 'ON ARVs' state within the quarter
275
+ # and having a TB WITHIN THE LAST 2 YEARS observation at the HIV staging encounter on the initiation date
276
+ cohort_struct.tb_within_the_last_two_years = tb_within_the_last_two_years(cohort_struct.current_episode_of_tb,
277
+ start_date, end_date)
278
+ cohort_struct.cum_tb_within_the_last_two_years = tb_within_the_last_two_years(
279
+ cohort_struct.cum_current_episode_of_tb, cum_start_date, end_date
280
+ )
281
+ cohort_struct.quarterly_tb_within_the_last_two_years = tb_within_the_last_two_years(
282
+ cohort_struct.quarterly_current_episode_of_tb, quarter_start_date, end_date
283
+ )
284
+
285
+ # No TB
286
+ # total_registered - (current_episode - tb_within_the_last_two_years)
287
+ cohort_struct.no_tb = no_tb(cohort_struct.total_registered, cohort_struct.tb_within_the_last_two_years,
288
+ cohort_struct.current_episode_of_tb)
289
+ cohort_struct.cum_no_tb = cum_no_tb(cohort_struct.cum_total_registered,
290
+ cohort_struct.cum_tb_within_the_last_two_years, cohort_struct.cum_current_episode_of_tb)
291
+ cohort_struct.quarterly_no_tb = cum_no_tb(cohort_struct.quarterly_total_registered,
292
+ cohort_struct.quarterly_tb_within_the_last_two_years, cohort_struct.quarterly_current_episode_of_tb)
293
+
294
+ # Kaposis Sarcoma
295
+ #
296
+ # Unique PatientProgram entries at the current location for those patients with at least one state ON ARVs
297
+ # and earliest start date of the 'ON ARVs' state within the quarter and having a KAPOSIS SARCOMA observation
298
+ # at the HIV staging encounter on the initiation date
299
+ cohort_struct.kaposis_sarcoma = kaposis_sarcoma(start_date, end_date)
300
+ cohort_struct.cum_kaposis_sarcoma = kaposis_sarcoma(cum_start_date, end_date)
301
+ cohort_struct.quarterly_kaposis_sarcoma = kaposis_sarcoma(quarter_start_date, end_date)
302
+
303
+ # From this point going down: we update temp_earliest_start_date cum_outcome field to have the latest Cumulative outcome
304
+ update_cum_outcome(end_date)
305
+ update_tb_status(end_date)
306
+ update_patient_side_effects(end_date)
307
+
308
+ # Total Alive and On ART
309
+ # Unique PatientProgram entries at the current location for those patients with at least one state
310
+ # ON ARVs and earliest start date of the 'ON ARVs' state less than or equal to end date of quarter
311
+ # and latest state is ON ARVs (Excluding defaulters)
312
+ cohort_struct.total_alive_and_on_art = get_outcome('On antiretrovirals')
313
+ cohort_struct.died_within_the_1st_month_of_art_initiation = died_in('1st month')
314
+ cohort_struct.died_within_the_2nd_month_of_art_initiation = died_in('2nd month')
315
+ cohort_struct.died_within_the_3rd_month_of_art_initiation = died_in('3rd month')
316
+ cohort_struct.died_after_the_3rd_month_of_art_initiation = died_in('4+ months')
317
+ cohort_struct.died_total = get_outcome('Patient died')
318
+ cohort_struct.defaulted = get_outcome('Defaulted')
319
+ cohort_struct.stopped_art = get_outcome('Treatment stopped')
320
+ cohort_struct.transfered_out = get_outcome('Patient transferred out')
321
+ cohort_struct.unknown_outcome = get_outcome('Pre-ART (Continue)')
322
+
323
+ # ARV Regimen category
324
+ # Alive and On ART and Value Coded of the latest 'Regimen Category' Observation
325
+ # of each patient that is linked to the Dispensing encounter in the reporting period
326
+
327
+ prescriptions = cal_regimem_category(cohort_struct.total_alive_and_on_art, end_date)
328
+
329
+ # concepts = ->(names) { ::ConceptName.where(name: names).select(:concept_id) }
330
+ # drugs = ->(concepts) { ::Drug.where(concept: concepts).select(:drug_id).collect(&:drug_id) }
331
+
332
+ # lpv_granules = drugs[concepts[['LPV/r Pellets', 'LPV/r Granules']]]
333
+ # lpv_tabs = drugs[concepts['LPV/r']]
334
+
335
+ cohort_struct.zero_p = filter_prescriptions_by_regimen(prescriptions, '0P')
336
+ cohort_struct.zero_a = filter_prescriptions_by_regimen(prescriptions, '0A')
337
+ cohort_struct.two_p = filter_prescriptions_by_regimen(prescriptions, '2P')
338
+ cohort_struct.two_a = filter_prescriptions_by_regimen(prescriptions, '2A')
339
+ cohort_struct.four_a = filter_prescriptions_by_regimen(prescriptions, '4A')
340
+ cohort_struct.four_pp = filter_prescriptions_by_regimen(prescriptions, '4PP')
341
+ cohort_struct.four_pa = filter_prescriptions_by_regimen(prescriptions, '4PA')
342
+ cohort_struct.five_a = filter_prescriptions_by_regimen(prescriptions, '5A')
343
+ cohort_struct.six_a = filter_prescriptions_by_regimen(prescriptions, '6A')
344
+ cohort_struct.seven_a = filter_prescriptions_by_regimen(prescriptions, '7A')
345
+ cohort_struct.eight_a = filter_prescriptions_by_regimen(prescriptions, '8A')
346
+ cohort_struct.nine_a = filter_prescriptions_by_regimen(prescriptions, '9A')
347
+ cohort_struct.nine_pp = filter_prescriptions_by_regimen(prescriptions, '9PP')
348
+ cohort_struct.nine_pa = filter_prescriptions_by_regimen(prescriptions, '9PA')
349
+ cohort_struct.ten_a = filter_prescriptions_by_regimen(prescriptions, '10A')
350
+ cohort_struct.eleven_a = filter_prescriptions_by_regimen(prescriptions, '11A')
351
+ cohort_struct.eleven_pp = filter_prescriptions_by_regimen(prescriptions, '11PP')
352
+ cohort_struct.eleven_pa = filter_prescriptions_by_regimen(prescriptions, '11PA')
353
+ cohort_struct.twelve_a = filter_prescriptions_by_regimen(prescriptions, '12A')
354
+ cohort_struct.twelve_pp = filter_prescriptions_by_regimen(prescriptions, '12PP')
355
+ cohort_struct.twelve_pa = filter_prescriptions_by_regimen(prescriptions, '12PA')
356
+ cohort_struct.thirteen_a = filter_prescriptions_by_regimen(prescriptions, '13A')
357
+ cohort_struct.fourteen_pp = filter_prescriptions_by_regimen(prescriptions, '14PP')
358
+ cohort_struct.fourteen_pa = filter_prescriptions_by_regimen(prescriptions, '14PA')
359
+ cohort_struct.fourteen_a = filter_prescriptions_by_regimen(prescriptions, '14A')
360
+ cohort_struct.fifteen_pp = filter_prescriptions_by_regimen(prescriptions, '15PP')
361
+ cohort_struct.fifteen_pa = filter_prescriptions_by_regimen(prescriptions, '15PA')
362
+ cohort_struct.fifteen_a = filter_prescriptions_by_regimen(prescriptions, '15A')
363
+ cohort_struct.sixteen_p = filter_prescriptions_by_regimen(prescriptions, '16P')
364
+ cohort_struct.sixteen_a = filter_prescriptions_by_regimen(prescriptions, '16A')
365
+ cohort_struct.seventeen_pp = filter_prescriptions_by_regimen(prescriptions, '17PP')
366
+ cohort_struct.seventeen_pa = filter_prescriptions_by_regimen(prescriptions, '17PA')
367
+ cohort_struct.seventeen_a = filter_prescriptions_by_regimen(prescriptions, '17A')
368
+ cohort_struct.unknown_regimen = filter_prescriptions_by_regimen(prescriptions, 'unknown_regimen')
369
+
370
+ # Total patients with side effects:
371
+ # Alive and On ART patients with DRUG INDUCED observations during their last HIV CLINIC CONSULTATION encounter up to the reporting period
372
+
373
+ with_se, without_se, se_unknowns = patients_side_effects_status(cohort_struct.total_alive_and_on_art, end_date)
374
+ cohort_struct.total_patients_with_side_effects = with_se
375
+ cohort_struct.total_patients_without_side_effects = without_se
376
+ cohort_struct.unknown_side_effects = se_unknowns
377
+
378
+ # TB Status
379
+ # Alive and On ART with 'TB Status' observation value of 'TB not Suspected' or 'TB Suspected'
380
+ # or 'TB confirmed and on Treatment', or 'TB confirmed and not on Treatment' or 'Unknown TB status'
381
+ # during their latest HIV Clinic Consultaiton encounter in the reporting period
382
+ write_tb_status_indicators(cohort_struct, cohort_struct.total_alive_and_on_art, start_date, end_date)
383
+
384
+ # ART adherence
385
+ #
386
+ # Alive and On ART with value of their 'Drug order adherence" observation during their latest Adherence
387
+ # encounter in the reporting period between 95 and 105
388
+ adherent, not_adherent, unknown_adherence = latest_art_adherence(cohort_struct.total_alive_and_on_art,
389
+ start_date, end_date)
390
+ cohort_struct.patients_with_0_6_doses_missed_at_their_last_visit = adherent
391
+ cohort_struct.patients_with_7_plus_doses_missed_at_their_last_visit = not_adherent
392
+ cohort_struct.patients_with_unknown_adhrence = unknown_adherence
393
+
394
+ # Pregnant and breastfeeding status during Consultaiton
395
+ cohort_struct.total_pregnant_women = total_pregnant_women(cohort_struct.total_alive_and_on_art, start_date,
396
+ end_date)
397
+ cohort_struct.total_breastfeeding_women = total_breastfeeding_women(cohort_struct.total_alive_and_on_art,
398
+ cohort_struct.total_pregnant_women, start_date, end_date)
399
+ cohort_struct.total_other_patients = total_other_patients(cohort_struct.total_alive_and_on_art,
400
+ cohort_struct.total_breastfeeding_women, cohort_struct.total_pregnant_women)
401
+
402
+ # Patients with CPT dispensed at least once before end of quarter and on ARVs
403
+ cohort_struct.total_patients_on_arvs_and_cpt = total_patients_on_arvs_and_cpt(
404
+ cohort_struct.total_alive_and_on_art, start_date, end_date
405
+ )
406
+
407
+ # Patients with IPT dispensed at least once before end of quarter and on ARVS
408
+ cohort_struct.total_patients_on_arvs_and_ipt = total_patients_on_arvs_and_ipt(
409
+ cohort_struct.total_alive_and_on_art, start_date, end_date
410
+ )
411
+
412
+ # Patients on family planning methods at least once before end of quarter and on ARVs
413
+ cohort_struct.total_patients_on_family_planning = total_patients_on_family_planning(
414
+ cohort_struct.total_alive_and_on_art, quarter_start_date, end_date
415
+ )
416
+
417
+ # Patients whose BP was screened and are above 30 years least once before end of quarter and on ARVs
418
+ cohort_struct.total_patients_with_screened_bp = total_patients_with_screened_bp(
419
+ total_patients_alive_and_on_art_above_30_years(cohort_struct.total_alive_and_on_art,
420
+ end_date), start_date, end_date
421
+ )
422
+
423
+ # Patients who started TPT in current reporting period
424
+ tpt = MalawiHivProgramReports::Cohort::Tpt.new(start_date:, end_date:, occupation:,
425
+ location: @location)
426
+ # tpt newly initiated has a property last_tpt_start_date we need to use that to get those clients
427
+ cohort_struct.newly_initiated_on_3hp = tpt.newly_initiated_on_3hp.select do |hash|
428
+ hash['last_tpt_start_date'].nil?
429
+ end
430
+ cohort_struct.newly_initiated_on_ipt = tpt.newly_initiated_on_ipt.select do |hash|
431
+ hash['last_tpt_start_date'].nil?
432
+ end
433
+ time_ended = Time.now.strftime('%Y-%m-%d %H:%M:%S')
434
+ puts "Started at: #{time_started}. Finished at: #{time_ended}. Total time in minutes: #{(Time.parse(time_ended) - Time.parse(time_started)) / 60}"
435
+ Rails.logger.info "Started at: #{time_started}. Finished at: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}. Total time in minutes: #{(Time.parse(time_ended) - Time.parse(time_started)) / 60}"
436
+ cohort_struct
437
+ end
438
+
439
+ # private
440
+
441
+ def get_disaggregated_cohort(start_date, end_date, gender, ag)
442
+ case ag
443
+ when '50+ years'
444
+ diff = [50, 1000]
445
+ iu = 'year'
446
+ when /years/i
447
+ diff = ag.sub(' years', '').split('-')
448
+ iu = 'year'
449
+ when /months/i
450
+ diff = ag.sub(' months', '').split('-')
451
+ iu = 'month'
452
+ else
453
+ case gender
454
+ when 'M'
455
+ diff = [0, 1000]
456
+ iu = 'year'
457
+ gender = 'M'
458
+ when 'FNP'
459
+ diff = [0, 1000]
460
+ iu = 'year'
461
+ gender = 'F'
462
+ when 'FP'
463
+ diff = [0, 1000]
464
+ iu = 'year'
465
+ gender = 'F'
466
+ when 'FBf'
467
+ diff = [0, 1000]
468
+ iu = 'year'
469
+ gender = 'F'
470
+ end
471
+ end
472
+
473
+ data = ActiveRecord::Base.connection.select_all(
474
+ "SELECT patient_id FROM temp_earliest_start_date
475
+ WHERE earliest_start_date BETWEEN '#{start_date.to_date}' AND '#{end_date.to_date}'
476
+ AND (earliest_start_date) = (date_enrolled) AND gender = '#{gender.first}' #{site_manager(operator: 'AND',
477
+ column: 'site_id', location: @location)}
478
+ AND timestampdiff(#{iu}, birthdate, date_enrolled) BETWEEN #{diff[0].to_i} AND #{diff[1].to_i}"
479
+ )
480
+
481
+ data1 = ActiveRecord::Base.connection.select_all(
482
+ "SELECT t1.patient_id
483
+ FROM temp_earliest_start_date t1
484
+ INNER JOIN temp_patient_outcomes t2 ON t1.patient_id = t2.patient_id #{site_manager(operator: 'AND',
485
+ column: 't2.site_id', location: @location)}
486
+ WHERE date_enrolled <= '#{end_date.to_date}' AND gender = '#{gender.first}'
487
+ AND cum_outcome = 'On antiretrovirals' #{site_manager(operator: 'AND', column: 't1.site_id',
488
+ location: @location)}
489
+ AND timestampdiff(#{iu}, birthdate, date_enrolled) BETWEEN #{diff[0].to_i} AND #{diff[1].to_i}"
490
+ )
491
+
492
+ ::EncounterType.find_by_name('DISPENSING').id
493
+ amount_dispensed = concept('Amount dispensed').concept_id
494
+ ipt_drug_ids = ::Drug.find_all_by_concept_id(656).map(&:drug_id)
495
+
496
+ patient_ids = []
497
+ (data1 || {}).each_key do |x|
498
+ patient_ids << x['patient_id'].to_i
499
+ end
500
+
501
+ unless patient_ids.blank?
502
+ data2 = ActiveRecord::Base.connection.select_all(
503
+ "SELECT e.patient_id
504
+ FROM encounter e
505
+ INNER JOIN temp_patient_outcomes o ON o.patient_id = e.patient_id
506
+ AND o.cum_outcome = 'On antiretrovirals' #{site_manager(operator: 'AND', column: 'o.site_id',
507
+ location: @location)}
508
+ INNER JOIN obs ON obs.encounter_id = e.encounter_id
509
+ AND obs.concept_id = #{amount_dispensed} #{site_manager(operator: 'AND', column: 'obs.site_id',
510
+ location: @location)}
511
+ WHERE value_drug IN(#{ipt_drug_ids.join(',')})
512
+ AND e.patient_id IN(#{patient_ids.join(',')})
513
+ AND e.encounter_datetime BETWEEN '#{start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
514
+ AND '#{end_date.to_date.strftime('%Y-%m-%d 23:59:59')}' #{site_manager(operator: 'AND',
515
+ column: 'e.site_id', location: @location)}
516
+ GROUP BY e.patient_id"
517
+ )
518
+ end
519
+
520
+ [data&.length || 0, data1&.length || 0, data2.length || 0, 0]
521
+ end
522
+
523
+ def patient_with_missing_start_reasons(start_date, end_date)
524
+ art_patients = ActiveRecord::Base.connection.select_all(
525
+ "SELECT e.*, #{function_manager(function: 'patient_reason_for_starting_art_text', location: @location,
526
+ args: "e.patient_id, #{@location}")} reason
527
+ FROM temp_earliest_start_date e
528
+ WHERE date_enrolled BETWEEN '#{start_date.to_date}' AND '#{end_date.to_date}' #{site_manager(operator: 'AND',
529
+ column: 'e.site_id', location: @location)}"
530
+ )
531
+
532
+ data = {}
533
+ art_patients.each do |p|
534
+ patient = ::Patient.find(p['patient_id'].to_i)
535
+ reason_for_starting = p['reason']
536
+ next unless reason_for_starting.blank?
537
+
538
+ data[patient.patient_id] = {
539
+ arv_number: patient.arv_number,
540
+ earliest_start_date: (begin
541
+ p['earliest_start_date'].to_date
542
+ rescue StandardError
543
+ nil
544
+ end),
545
+ date_enrolled: (begin
546
+ p['date_enrolled'].to_date
547
+ rescue StandardError
548
+ nil
549
+ end),
550
+ name: patient.person.name,
551
+ gender: patient.person.gender,
552
+ birthdate: patient.person.birth_date,
553
+ outcome: p['outcome']
554
+ }
555
+ end
556
+
557
+ data
558
+ rescue StandardError
559
+ raise 'Try running the revised cohort before this report'
560
+ end
561
+
562
+ def on_art_patients_with_no_arvs_dispensations(start_date, end_date)
563
+ arv_drugs = MedicationService.arv_drugs
564
+ arv_drugs = arv_drugs.map(&:concept_id)
565
+
566
+ start_date.to_date
567
+ end_date.to_date
568
+
569
+ data = ActiveRecord::Base.connection.select_all(
570
+ "SELECT patient_id
571
+ FROM orders o
572
+ INNER JOIN drug_order drg ON drg.order_id = o.order_id #{site_manager(operator: 'AND', column: 'drg.site_id',
573
+ location: @location)}
574
+ AND o.voided = 0
575
+ WHERE drug_inventory_id IN(
576
+ SELECT drug_id FROM drug
577
+ WHERE concept_id IN(#{arv_drugs.join(',')})
578
+ ) #{site_manager(operator: 'AND', column: 'o.site_id', location: @location)}
579
+ GROUP BY patient_id"
580
+ )
581
+
582
+ patient_ids = data.map { |d| d['patient_id'].to_i }
583
+
584
+ begin
585
+ patients = ActiveRecord::Base.connection.select_all(
586
+ "SELECT * FROM temp_earliest_start_date WHERE patient_id NOT IN(#{patient_ids.join(',')}) #{site_manager(
587
+ operator: 'AND', column: 'site_id', location: @location
588
+ )}"
589
+ )
590
+ rescue StandardError
591
+ raise 'Try running the revised cohort before this report'
592
+ end
593
+
594
+ reason_for_starting = concept('REASON FOR ART ELIGIBILITY')
595
+ data = {}
596
+
597
+ (patients || []).each do |p|
598
+ patient = ::Patient.find(p['patient_id'].to_i)
599
+ reason_for_starting = ::PatientService.reason_for_art_eligibility(patient)
600
+ # next unless reason_for_starting.blank?
601
+
602
+ patient_obj = ::PatientService.get_patient(patient.person)
603
+ data[patient_obj.patient_id] = {
604
+ arv_number: patient_obj.arv_number,
605
+ earliest_start_date: p['earliest_start_date'],
606
+ date_enrolled: p['date_enrolled'].to_date,
607
+ name: patient_obj.name,
608
+ gender: patient_obj.sex,
609
+ birthdate: patient_obj.birth_date,
610
+ outcome: p['outcome']
611
+ }
612
+ end
613
+
614
+ data
615
+ end
616
+
617
+ def patients_with_pre_art_or_unknown_outcome(_start_date, _end_date)
618
+ begin
619
+ patients = ActiveRecord::Base.connection.select_all(
620
+ "SELECT e.*, cum_outcome, #{function_manager(function: 'patient_reason_for_starting_art_text',
621
+ location: @location, args: "e.patient_id, #{@location}")} reason_for_starting
622
+ FROM temp_patient_outcomes o
623
+ INNER JOIN temp_earliest_start_date e ON e.patient_id = o.patient_id #{site_manager(operator: 'AND',
624
+ column: 'e.site_id', location: @location)}
625
+ WHERE cum_outcome LIKE '%Pre-%' OR cum_outcome LIKE '%Unknown%' #{site_manager(operator: 'AND',
626
+ column: 'o.site_id', location: @location)}"
627
+ )
628
+ rescue StandardError
629
+ raise 'Try running the revised cohort before this report'
630
+ end
631
+
632
+ data = {}
633
+
634
+ (patients || []).each do |p|
635
+ ::Patient.find(p['patient_id'].to_i)
636
+
637
+ patient_outcome = p['cum_outcome']
638
+ person = ::Person.find(p['patient_id'])
639
+
640
+ patient_obj = ::PatientService.get_patient(person)
641
+ data[patient_obj.patient_id] = {
642
+ arv_number: patient_obj.arv_number,
643
+ earliest_start_date: (begin
644
+ p['earliest_start_date'].to_date
645
+ rescue StandardError
646
+ nil
647
+ end),
648
+ date_enrolled: (begin
649
+ p['date_enrolled'].to_date
650
+ rescue StandardError
651
+ nil
652
+ end),
653
+ name: patient_obj.name,
654
+ gender: patient_obj.sex,
655
+ birthdate: patient_obj.birth_date,
656
+ reason_for_starting: p['reason_for_starting'],
657
+ outcome: patient_outcome['outcome']
658
+ }
659
+ end
660
+
661
+ data
662
+ end
663
+
664
+ STATE_DIED = 3
665
+ STATE_ON_TREATMENT = 7
666
+
667
+ def load_data_into_temp_earliest_start_date(end_date, occupation = nil)
668
+ load_data_into_temp_cohort_members_table(end_date)
669
+ ActiveRecord::Base.connection.execute <<~SQL
670
+ INSERT INTO temp_earliest_start_date
671
+ SELECT patient_id, site_id, date_enrolled, earliest_start_date, recorded_start_date, birthdate, birthdate_estimated, death_date, gender, age_at_initiation, age_in_days, reason_for_starting_art
672
+ FROM temp_cohort_members #{occupation_filter(occupation:, field_name: 'occupation')} #{site_manager(operator: min_filt(occupation).to_s, column: 'site_id', location: @location)}
673
+ SQL
674
+ end
675
+
676
+ # rubocop:disable Metrics/MethodLength
677
+ def load_data_into_temp_cohort_members_table(end_date)
678
+ end_date = ActiveRecord::Base.connection.quote(end_date)
679
+
680
+ type_of_patient_concept = concept('Type of patient').concept_id
681
+ new_patient_concept = concept('New patient').concept_id
682
+ drug_refill_concept = concept('Drug refill').concept_id
683
+ external_concept = concept('External Consultation').concept_id
684
+ program_id = program('HIV program').id
685
+
686
+ ActiveRecord::Base.connection.execute <<~SQL
687
+ INSERT INTO temp_cohort_members
688
+ SELECT patient_program.patient_id, #{@location} as site_id,
689
+ DATE(MIN(art_order.start_date)) AS date_enrolled,
690
+ DATE(COALESCE(MIN(art_start_date_obs.value_datetime), MIN(art_order.start_date))) AS earliest_start_date,
691
+ DATE(MIN(art_start_date_obs.value_datetime)) AS recorded_start_date,
692
+ person.birthdate,
693
+ #{@adapter == 'mysql2' ? 'person.birthdate_estimated' : '(CASE WHEN person.birthdate_estimated = 0 THEN false ELSE true END)'} AS birthdate_estimated,
694
+ person.death_date,
695
+ person.gender, #{
696
+ if @adapter == 'mysql2'
697
+ <<~SQL
698
+ IF(person.birthdate IS NOT NULL, TIMESTAMPDIFF(YEAR, person.birthdate, DATE(COALESCE(art_start_date_obs.value_datetime, MIN(art_order.start_date)))), NULL) AS age_at_initiation,
699
+ IF(person.birthdate IS NOT NULL, TIMESTAMPDIFF(DAY, person.birthdate, DATE(COALESCE(art_start_date_obs.value_datetime, MIN(art_order.start_date)))), NULL) AS age_in_days,
700
+ SQL
701
+ else
702
+ <<~SQL
703
+ CASE
704
+ WHEN person.birthdate IS NOT NULL THEN
705
+ EXTRACT(YEAR FROM AGE(COALESCE(MIN(art_start_date_obs.value_datetime)::DATE, MIN(art_order.start_date)), person.birthdate))
706
+ ELSE NULL
707
+ END AS age_at_initiation,
708
+ CASE
709
+ WHEN person.birthdate IS NOT NULL THEN
710
+ EXTRACT(DAY FROM AGE(COALESCE(MIN(art_start_date_obs.value_datetime)::DATE, MIN(art_order.start_date)), person.birthdate))
711
+ ELSE NULL
712
+ END AS age_in_days,
713
+ SQL
714
+ end
715
+ }
716
+ (SELECT value_coded FROM obs
717
+ WHERE concept_id = 7563 AND person_id = patient_program.patient_id AND voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
718
+ ORDER BY obs_datetime DESC, date_created DESC LIMIT 1) as reason_for_starting_art,
719
+ pa.value AS occupation
720
+ FROM patient_program
721
+ INNER JOIN person ON person.person_id = patient_program.patient_id #{site_manager(operator: 'AND', column: 'person.site_id', location: @location)}
722
+ LEFT JOIN (#{current_occupation_query}) pa ON pa.person_id = patient_program.patient_id #{site_manager(operator: 'AND', column: 'pa.site_id', location: @location)}
723
+ LEFT JOIN patient_state AS outcome
724
+ ON outcome.patient_program_id = patient_program.patient_program_id #{site_manager(operator: 'AND', column: 'outcome.site_id', location: @location)}
725
+ LEFT JOIN encounter AS clinic_registration_encounter
726
+ ON clinic_registration_encounter.encounter_type = (
727
+ SELECT encounter_type_id FROM encounter_type WHERE LOWER(name) = LOWER('HIV CLINIC REGISTRATION') LIMIT 1
728
+ )
729
+ AND clinic_registration_encounter.patient_id = patient_program.patient_id
730
+ AND clinic_registration_encounter.program_id = patient_program.program_id
731
+ AND clinic_registration_encounter.encounter_datetime < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
732
+ AND clinic_registration_encounter.voided = 0 #{site_manager(operator: 'AND', column: 'clinic_registration_encounter.site_id', location: @location)}
733
+ LEFT JOIN obs AS art_start_date_obs
734
+ ON art_start_date_obs.concept_id = 2516
735
+ AND art_start_date_obs.person_id = patient_program.patient_id
736
+ AND art_start_date_obs.voided = 0 #{site_manager(operator: 'AND', column: 'art_start_date_obs.site_id', location: @location)}
737
+ AND art_start_date_obs.obs_datetime < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
738
+ AND art_start_date_obs.encounter_id = clinic_registration_encounter.encounter_id
739
+ /* TODO: Re-enable the following condition. Has been removed because LLH and PIH
740
+ were noted to be dropping patients because of it. Seems these sites may have orders
741
+ without corresponding encounters. Adding this condition bumps up performance a bit. */
742
+ /* INNER JOIN encounter AS prescription_encounter
743
+ ON prescription_encounter.patient_id = patient_program.patient_id
744
+ AND prescription_encounter.program_id = patient_program.program_id
745
+ AND prescription_encounter.encounter_datetime < DATE(#{end_date}) + INTERVAL 1 DAY
746
+ AND prescription_encounter.encounter_type IN (SELECT encounter_type_id FROM encounter_type WHERE name LIKE 'Treatment')
747
+ AND prescription_encounter.voided = 0 */
748
+ LEFT JOIN temp_register_start_date AS patient_type_obs
749
+ ON patient_type_obs.patient_id = patient_program.patient_id #{site_manager(operator: 'AND', column: 'patient_type_obs.site_id', location: @location)}
750
+ /*INNER JOIN orders AS art_order
751
+ ON art_order.patient_id = patient_program.patient_id
752
+ -- AND art_order.encounter_id = prescription_encounter.encounter_id
753
+ AND art_order.concept_id IN (SELECT concept_id FROM concept_set WHERE concept_set = 1085)
754
+ AND art_order.start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
755
+ AND art_order.order_type_id = #{order_type('Drug order').id}
756
+ AND art_order.start_date >= COALESCE(patient_type_obs.start_date, DATE('1901-01-01'))
757
+ AND art_order.voided = 0 #{site_manager(operator: 'AND', column: 'art_order.site_id', location: @location)}
758
+ INNER JOIN drug_order
759
+ ON drug_order.order_id = art_order.order_id
760
+ AND drug_order.quantity > 0 #{site_manager(operator: 'AND', column: 'drug_order.site_id', location: @location)} */
761
+ INNER JOIN temp_order_details AS art_order ON art_order.patient_id = patient_program.patient_id #{site_manager(operator: 'AND', column: 'art_order.site_id', location: @location)}
762
+ WHERE patient_program.voided = 0
763
+ AND outcome.voided = 0
764
+ AND patient_program.program_id = 1
765
+ AND outcome.state = 7
766
+ AND outcome.start_date IS NOT NULL #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
767
+ /*AND patient_program.patient_id NOT IN (
768
+ SELECT e.patient_id FROM encounter e
769
+ LEFT JOIN (SELECT * FROM obs WHERE concept_id = #{type_of_patient_concept} AND voided = 0 AND value_coded = #{new_patient_concept}) AS new_patient ON e.patient_id = new_patient.person_id
770
+ LEFT JOIN (SELECT * FROM obs WHERE concept_id = #{type_of_patient_concept} AND voided = 0 AND value_coded = #{drug_refill_concept}) AS refill ON e.patient_id = refill.person_id
771
+ LEFT JOIN (SELECT * FROM obs WHERE concept_id = #{type_of_patient_concept} AND voided = 0 AND value_coded = #{external_concept}) AS external ON e.patient_id = external.person_id
772
+ WHERE e.program_id = #{program_id} AND (refill.value_coded IS NOT NULL OR external.value_coded IS NOT NULL)
773
+ AND new_patient.value_coded IS NULL
774
+ AND e.encounter_datetime < DATE(#{end_date}) + INTERVAL 1 DAY
775
+ AND e.encounter_type IN (SELECT encounter_type_id FROM encounter_type WHERE name = 'REGISTRATION' AND retired = 0)
776
+ GROUP BY e.patient_id
777
+ )*/
778
+ GROUP by patient_program.patient_id #{@adapter == 'mysql2' ? '' : ',person.birthdate, person.birthdate_estimated, person.death_date, person.gender, pa.value'}
779
+ HAVING #{@adapter == 'mysql2' ? 'date_enrolled' : 'art_order.start_date'} <= #{end_date}
780
+ SQL
781
+ remove_drug_refills_and_external_consultation(end_date)
782
+ end
783
+ # rubocop:enable Metrics/MethodLength
784
+
785
+ ##
786
+ # This will hold crucial information for cohort members
787
+ # rubocop:disable Metrics/MethodLength
788
+ # rubocop:disable Metrics/AbcSize
789
+ def create_temp_cohort_members_table
790
+ ActiveRecord::Base.connection.execute('DROP TABLE IF EXISTS temp_cohort_members')
791
+ exe_temp_cohort_members_table(adapter: @adapter)
792
+
793
+ ActiveRecord::Base.connection.execute(
794
+ 'CREATE INDEX member_id_index ON temp_cohort_members (patient_id)'
795
+ )
796
+ ActiveRecord::Base.connection.execute(
797
+ 'CREATE INDEX member_site_index ON temp_cohort_members (site_id)'
798
+ )
799
+ ActiveRecord::Base.connection.execute(
800
+ 'CREATE INDEX member_id_site_index ON temp_cohort_members (patient_id, site_id)'
801
+ )
802
+ ActiveRecord::Base.connection.execute(
803
+ 'CREATE INDEX member_enrolled_index ON temp_cohort_members (date_enrolled)'
804
+ )
805
+ ActiveRecord::Base.connection.execute(
806
+ 'CREATE INDEX member_date_enrolled_index ON temp_cohort_members (patient_id, date_enrolled)'
807
+ )
808
+ ActiveRecord::Base.connection.execute(
809
+ 'CREATE INDEX member_start_date_index ON temp_cohort_members (earliest_start_date)'
810
+ )
811
+ ActiveRecord::Base.connection.execute(
812
+ 'CREATE INDEX member_start_date__date_enrolled_index ON temp_cohort_members (patient_id, earliest_start_date, date_enrolled, gender)'
813
+ )
814
+ ActiveRecord::Base.connection.execute(
815
+ 'CREATE INDEX member_reason ON temp_cohort_members (reason_for_starting_art)'
816
+ )
817
+ ActiveRecord::Base.connection.execute(
818
+ 'CREATE INDEX member_birthdate_idx ON temp_cohort_members (birthdate)'
819
+ )
820
+ ActiveRecord::Base.connection.execute(
821
+ 'CREATE INDEX member_occupation_idx ON temp_cohort_members (birthdate)'
822
+ )
823
+ end
824
+ # rubocop:enable Metrics/AbcSize
825
+ # rubocop:enable Metrics/MethodLength
826
+
827
+ def create_temp_other_patient_types(_end_date)
828
+ type_of_patient_concept = concept('Type of patient').concept_id
829
+ drug_refill_concept = concept('Drug refill').concept_id
830
+ external_concept = concept('External consultation').concept_id
831
+ exe_temp_other_patient_types(adapter: @adapter)
832
+
833
+ ActiveRecord::Base.connection.execute <<~SQL
834
+ INSERT INTO temp_other_patient_types (patient_id, site_id)
835
+ SELECT pp.patient_id as patient_id, pp.site_id as site_id
836
+ FROM patient_program pp
837
+ INNER JOIN obs o ON pp.patient_id = o.person_id AND o.concept_id = #{type_of_patient_concept} #{site_manager(operator: 'AND', column: 'o.site_id', location: @location)}
838
+ AND #{in_manager(column: 'o.value_coded', values: [drug_refill_concept, external_concept])}
839
+ AND o.voided = 0
840
+ WHERE pp.program_id = 1
841
+ AND pp.voided = 0
842
+ #{site_manager(operator: 'AND', column: 'pp.site_id', location: @location)}
843
+ GROUP BY pp.patient_id #{@adapter == 'mysql2' ? '' : ', pp.site_id'}
844
+ SQL
845
+ end
846
+
847
+ def drop_temp_other_patient_types
848
+ ActiveRecord::Base.connection.execute <<~SQL
849
+ DROP TABLE IF EXISTS temp_other_patient_types
850
+ SQL
851
+ end
852
+
853
+ def drop_temp_order_details
854
+ ActiveRecord::Base.connection.execute <<~SQL
855
+ DROP TABLE IF EXISTS temp_order_details
856
+ SQL
857
+ end
858
+
859
+ def create_temp_order_details(end_date)
860
+ exe_temp_order_details_table(adapter: @adapter)
861
+ ActiveRecord::Base.connection.execute <<~SQL
862
+ INSERT INTO temp_order_details
863
+ SELECT o.patient_id, o.site_id, DATE(MIN(o.start_date)) start_date#{' '}
864
+ FROM orders o
865
+ INNER JOIN drug_order do ON do.order_id = o.order_id AND do.quantity > 0 #{site_manager(operator: 'AND', column: 'do.site_id', location: @location)}
866
+ LEFT JOIN temp_register_start_date trsd ON trsd.patient_id = o.patient_id #{site_manager(operator: 'AND', column: 'trsd.site_id', location: @location)}
867
+ WHERE o.concept_id IN (SELECT concept_id FROM concept_set WHERE concept_set = 1085)
868
+ AND o.start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')} AND o.start_date >= COALESCE(trsd.start_date, DATE('1901-01-01'))
869
+ and o.order_type_id = 1 ANd o.voided = 0 #{site_manager(operator: 'AND', column: 'o.site_id', location: @location)}
870
+ GROUP BY o.patient_id;
871
+ SQL
872
+ end
873
+
874
+ def create_temp_register_start_date_table(_end_date)
875
+ type_of_patient_concept = concept('Type of patient').concept_id
876
+ new_patient_concept = concept('New patient').concept_id
877
+ exe_temp_register_start_date_table(adapter: @adapter)
878
+ ActiveRecord::Base.connection.execute <<-SQL
879
+ INSERT INTO temp_register_start_date (patient_id, start_date, site_id)
880
+ SELECT pp.patient_id as patient_id, MIN(o.obs_datetime) AS start_date, pp.site_id as site_id
881
+ FROM patient_program pp
882
+ INNER JOIN temp_other_patient_types tmp ON tmp.patient_id = pp.patient_id
883
+ #{site_manager(operator: 'AND', column: 'pp.site_id', location: @location)}
884
+ INNER JOIN obs o ON pp.patient_id = o.person_id AND o.concept_id = #{type_of_patient_concept}
885
+ #{site_manager(operator: 'AND', column: 'o.site_id', location: @location)}
886
+ AND o.value_coded = #{new_patient_concept}
887
+ AND o.voided = 0
888
+ WHERE pp.program_id = 1
889
+ #{site_manager(operator: 'AND', column: 'pp.site_id', location: @location)}
890
+ GROUP BY pp.patient_id #{@adapter == 'mysql2' ? '' : ', pp.site_id'}
891
+ SQL
892
+ end
893
+
894
+ def drop_temp_register_start_date_table
895
+ ActiveRecord::Base.connection.execute <<~SQL
896
+ DROP TABLE IF EXISTS temp_register_start_date
897
+ SQL
898
+ end
899
+
900
+ def remove_drug_refills_and_external_consultation(end_date)
901
+ ActiveRecord::Base.connection.execute <<~SQL
902
+ DELETE FROM temp_cohort_members
903
+ WHERE #{in_manager(column: 'patient_id', values: drug_refills_and_external_consultation_list(end_date))}
904
+ SQL
905
+ end
906
+
907
+ # this just gives all clients who are truly external or drug refill
908
+ # rubocop:disable Metrics/MethodLength
909
+ # rubocop:disable Metrics/AbcSize
910
+ def drug_refills_and_external_consultation_list(end_date)
911
+ to_remove = [0]
912
+
913
+ type_of_patient_concept = concept('Type of patient').concept_id
914
+ new_patient_concept = concept('New patient').concept_id
915
+ drug_refill_concept = concept('Drug refill').concept_id
916
+ external_concept = concept('External consultation').concept_id
917
+ hiv_clinic_registration_id = ::EncounterType.find_by_name('HIV CLINIC REGISTRATION').encounter_type_id
918
+
919
+ ActiveRecord::Base.connection.select_all("SELECT e.patient_id
920
+ FROM temp_cohort_members e
921
+ LEFT JOIN encounter as hiv_registration ON hiv_registration.patient_id = e.patient_id
922
+ AND hiv_registration.encounter_datetime < DATE(#{end_date})
923
+ AND hiv_registration.encounter_type = #{hiv_clinic_registration_id}
924
+ AND hiv_registration.voided = 0 #{site_manager(operator: 'AND', column: 'hiv_registration.site_id',
925
+ location: @location)}
926
+ LEFT JOIN (SELECT * FROM obs WHERE concept_id = #{type_of_patient_concept} AND voided = 0 AND value_coded = #{new_patient_concept} AND obs_datetime < #{interval_manager(
927
+ date: end_date, value: 1, interval: 'DAY', operator: '+'
928
+ )} #{site_manager(operator: 'AND', column: 'site_id',
929
+ location: @location)}) AS new_patient ON e.patient_id = new_patient.person_id
930
+ LEFT JOIN (SELECT * FROM obs WHERE concept_id = #{type_of_patient_concept} AND voided = 0 AND value_coded = #{drug_refill_concept} AND obs_datetime < #{interval_manager(
931
+ date: end_date, value: 1, interval: 'DAY', operator: '+'
932
+ )} #{site_manager(operator: 'AND', column: 'site_id',
933
+ location: @location)}) AS refill ON e.patient_id = refill.person_id
934
+ LEFT JOIN (SELECT * FROM obs WHERE concept_id = #{type_of_patient_concept} AND voided = 0 AND value_coded = #{external_concept} AND DATE(obs_datetime) < #{interval_manager(
935
+ date: end_date, value: 1, interval: 'DAY', operator: '+'
936
+ )} #{site_manager(operator: 'AND', column: 'site_id',
937
+ location: @location)}) AS external ON e.patient_id = external.person_id
938
+ WHERE (refill.value_coded IS NOT NULL OR external.value_coded IS NOT NULL)
939
+ AND NOT (hiv_registration.encounter_id IS NOT NULL OR new_patient.value_coded IS NOT NULL)
940
+ GROUP BY e.patient_id
941
+ ORDER BY MAX(hiv_registration.encounter_datetime) DESC, MAX(refill.obs_datetime) DESC, MAX(external.obs_datetime) DESC;").each do |record|
942
+ to_remove << record['patient_id'].to_i
943
+ end
944
+ to_remove.join(',')
945
+ end
946
+ # rubocop:enable Metrics/MethodLength
947
+ # rubocop:enable Metrics/AbcSize
948
+
949
+ def create_tmp_patient_table
950
+ ActiveRecord::Base.connection.execute('DROP TABLE IF EXISTS temp_earliest_start_date')
951
+ exe_tmp_patient_table(adapter: @adapter)
952
+
953
+ ActiveRecord::Base.connection.execute(
954
+ 'CREATE INDEX patient_id_index ON temp_earliest_start_date (patient_id)'
955
+ )
956
+ ActiveRecord::Base.connection.execute(
957
+ 'CREATE INDEX site_id_tesd ON temp_earliest_start_date (site_id)'
958
+ )
959
+ ActiveRecord::Base.connection.execute(
960
+ 'CREATE INDEX patient_id__site_id_index ON temp_earliest_start_date (patient_id, site_id)'
961
+ )
962
+ ActiveRecord::Base.connection.execute(
963
+ 'CREATE INDEX date_enrolled_index ON temp_earliest_start_date (date_enrolled)'
964
+ )
965
+ ActiveRecord::Base.connection.execute(
966
+ 'CREATE INDEX patient_id__date_enrolled_index ON temp_earliest_start_date (patient_id, date_enrolled)'
967
+ )
968
+ ActiveRecord::Base.connection.execute(
969
+ 'CREATE INDEX earliest_start_date_index ON temp_earliest_start_date (earliest_start_date)'
970
+ )
971
+ ActiveRecord::Base.connection.execute(
972
+ 'CREATE INDEX earliest_start_date__date_enrolled_index ON temp_earliest_start_date (patient_id, earliest_start_date, date_enrolled, gender)'
973
+ )
974
+ ActiveRecord::Base.connection.execute(
975
+ 'CREATE INDEX idx_reason_for_art ON temp_earliest_start_date (reason_for_starting_art)'
976
+ )
977
+ ActiveRecord::Base.connection.execute(
978
+ 'CREATE INDEX birthdate_idx ON temp_earliest_start_date (birthdate)'
979
+ )
980
+ end
981
+
982
+ def update_cum_outcome(end_date)
983
+ MalawiHivProgramReports::Cohort::Outcomes.new(end_date:, definition: @outcomes_definition, location: @location)
984
+ .update_cummulative_outcomes
985
+ end
986
+
987
+ def update_tb_status(end_date)
988
+ ActiveRecord::Base.connection.execute(
989
+ 'DROP TABLE IF EXISTS temp_patient_tb_status'
990
+ )
991
+
992
+ ActiveRecord::Base.connection.execute <<~SQL
993
+ CREATE TABLE temp_patient_tb_status (
994
+ patient_id INT PRIMARY KEY,
995
+ site_id INT DEFAULT #{@location},
996
+ tb_status INT
997
+ )
998
+ SQL
999
+
1000
+ ActiveRecord::Base.connection.execute('CREATE INDEX tb_status_index ON temp_patient_tb_status (tb_status)')
1001
+ ActiveRecord::Base.connection.execute('CREATE INDEX site_id_tpts ON temp_patient_tb_status (site_id)')
1002
+ ActiveRecord::Base.connection.execute('CREATE INDEX patient_id_site_id_index ON temp_patient_tb_status (patient_id, site_id)')
1003
+ ActiveRecord::Base.connection.execute('CREATE INDEX patient_id_tb_status_index ON temp_patient_tb_status (patient_id, tb_status)')
1004
+
1005
+ ActiveRecord::Base.connection.execute <<~SQL
1006
+ INSERT INTO temp_patient_tb_status (patient_id, tb_status)
1007
+ SELECT e.patient_id, obs.value_coded
1008
+ FROM temp_earliest_start_date e
1009
+ INNER JOIN temp_patient_outcomes o ON o.patient_id = e.patient_id #{site_manager(operator: 'AND', column: 'o.site_id', location: @location)}
1010
+ RIGHT JOIN obs ON obs.person_id = o.patient_id #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1011
+ WHERE e.date_enrolled <= '#{end_date}' AND obs.obs_datetime <= '#{end_date} 23:59:59'
1012
+ AND LOWER(cum_outcome) = LOWER('On antiretrovirals') AND obs.voided = 0
1013
+ AND obs.concept_id = 7459 #{site_manager(operator: 'AND', column: 'e.site_id', location: @location)}
1014
+ AND obs.obs_datetime = (
1015
+ SELECT MAX(t.obs_datetime) FROM obs t WHERE t.concept_id = 7459 AND t.voided = 0
1016
+ AND t.person_id = e.patient_id AND t.obs_datetime <= '#{end_date} 23:59:59' #{site_manager(operator: 'AND', column: 't.site_id', location: @location)}
1017
+ ) GROUP BY e.patient_id #{@adapter == 'mysql2' ? '' : ', obs.value_coded'}
1018
+ SQL
1019
+ end
1020
+
1021
+ def update_patient_side_effects(end_date)
1022
+ MalawiHivProgramReports::Cohort::SideEffects.new.update_side_effects(end_date, @location)
1023
+ end
1024
+
1025
+ private
1026
+
1027
+ def total_patients_with_screened_bp(total_alive_and_on_art, _start_date, end_date)
1028
+ return 0 if total_alive_and_on_art.blank? || total_alive_and_on_art.empty?
1029
+
1030
+ bp_concepts = ::ConceptName.where(name: ['Systolic blood pressure', 'Diastolic blood pressure'])
1031
+ .select(:concept_id)
1032
+
1033
+ results = ActiveRecord::Base.connection.select_all <<~SQL
1034
+ SELECT o.person_id
1035
+ FROM obs o
1036
+ INNER JOIN (
1037
+ SELECT person_id, MAX(obs.obs_datetime) AS obs_datetime
1038
+ FROM obs
1039
+ WHERE voided = 0 AND concept_id IN (#{bp_concepts.to_sql})
1040
+ AND (value_text IS NOT NULL OR value_numeric IS NOT NULL)
1041
+ AND obs_datetime < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')} AND obs_datetime >= #{interval_manager(date: end_date, value: 12, interval: 'MONTH', operator: '-')}
1042
+ AND #{in_manager(column: 'person_id', values: total_alive_and_on_art)} #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1043
+ GROUP BY person_id
1044
+ ) AS max_obs
1045
+ ON max_obs.person_id = o.person_id
1046
+ AND max_obs.obs_datetime = o.obs_datetime
1047
+ WHERE o.voided = 0 #{site_manager(operator: 'AND', column: 'o.site_id', location: @location)}
1048
+ AND o.concept_id IN (#{bp_concepts.to_sql})
1049
+ AND (o.value_text IS NOT NULL OR o.value_numeric IS NOT NULL)
1050
+ GROUP BY o.person_id
1051
+ SQL
1052
+
1053
+ ((results.count.to_f / total_alive_and_on_art.count) * 100).to_i
1054
+ end
1055
+
1056
+ def total_patients_alive_and_on_art_above_30_years(total_alive_and_on_art, end_date)
1057
+ return nil if total_alive_and_on_art.blank?
1058
+ return nil if total_alive_and_on_art.empty?
1059
+
1060
+ results = ActiveRecord::Base.connection.select_all <<~SQL
1061
+ SELECT tesd.patient_id, #{@adapter == 'mysql2' ? "TIMESTAMPDIFF(YEAR, tesd.birthdate, DATE('#{end_date}')) AS age" : "(EXTRACT(YEAR FROM AGE(DATE('#{end_date}'), tesd.birthdate))) AS age"}
1062
+ FROM temp_earliest_start_date tesd
1063
+ WHERE #{in_manager(column: 'tesd.patient_id', values: total_alive_and_on_art.map { |r| r['patient_id'].to_i }.join(','))}
1064
+ GROUP BY tesd.patient_id
1065
+ HAVING #{@adapter == 'mysql2' ? 'age' : "(EXTRACT(YEAR FROM AGE(DATE('#{end_date}'), tesd.birthdate)))"} >= 30
1066
+ SQL
1067
+
1068
+ # map the results to patient ids
1069
+ results&.map { |r| r['patient_id'].to_i }
1070
+ end
1071
+
1072
+ def total_patients_on_family_planning(patients_list, start_date, end_date)
1073
+ patient_ids = []
1074
+ patient_list = []
1075
+
1076
+ (patients_list || []).each do |row|
1077
+ patient_ids << row['patient_id'].to_i
1078
+ end
1079
+
1080
+ return [] if patient_ids.blank?
1081
+
1082
+ all_women = ActiveRecord::Base.connection.select_all(
1083
+ "SELECT * FROM temp_earliest_start_date
1084
+ WHERE gender IN ('F','Female') AND patient_id IN (#{patient_ids.join(',')})
1085
+ AND date_enrolled BETWEEN '#{start_date.to_date}' AND '#{end_date.to_date}' #{site_manager(operator: 'AND',
1086
+ column: 'site_id', location: @location)}
1087
+ GROUP BY patient_id"
1088
+ )
1089
+
1090
+ (all_women || []).each do |patient|
1091
+ patient_list << patient['patient_id'].to_i
1092
+ end
1093
+
1094
+ return 0 if patient_list.blank?
1095
+
1096
+ hiv_clinic_consultation_encounter_type_id = encounter_type('HIV CLINIC CONSULTATION').encounter_type_id
1097
+ method_of_family_planning_concept_id = concept('Method of family planning').concept_id
1098
+ family_planning_action_to_take_concept_id = concept('Family planning, action to take').concept_id
1099
+ none_concept_id = [concept('None').concept_id, concept('No').concept_id]
1100
+
1101
+ results = ActiveRecord::Base.connection.select_all(
1102
+ "SELECT o.person_id
1103
+ FROM obs o
1104
+ INNER JOIN encounter e on e.encounter_id = o.encounter_id AND e.encounter_type = #{hiv_clinic_consultation_encounter_type_id} #{site_manager(
1105
+ operator: 'AND', column: 'e.site_id', location: @location
1106
+ )}
1107
+ WHERE o.voided = 0 AND e.voided = 0 #{site_manager(operator: 'AND', column: 'o.site_id', location: @location)}
1108
+ AND #{in_manager(column: 'o.concept_id',
1109
+ values: [family_planning_action_to_take_concept_id,
1110
+ method_of_family_planning_concept_id])}
1111
+ AND #{in_manager(column: 'o.value_coded', values: none_concept_id.join(','), negation: true)}
1112
+ AND #{in_manager(column: 'o.person_id', values: patient_list.join(','))}
1113
+ AND o.obs_datetime BETWEEN '#{start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
1114
+ AND '#{end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
1115
+ AND DATE(o.obs_datetime) = (SELECT max(date(obs.obs_datetime)) FROM obs obs
1116
+ WHERE obs.voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1117
+ AND #{in_manager(column: 'obs.concept_id',
1118
+ values: [family_planning_action_to_take_concept_id,
1119
+ method_of_family_planning_concept_id])}
1120
+ AND obs.obs_datetime BETWEEN '#{start_date.to_date.strftime('%Y-%m-%d 00:00:00')}' AND '#{end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
1121
+ AND obs.person_id = o.person_id) #{site_manager(operator: 'AND', column: 'o.site_id', location: @location)}
1122
+ GROUP BY o.person_id"
1123
+ )
1124
+
1125
+ begin
1126
+ ((results.count.to_f / patient_list.count) * 100).to_i
1127
+ rescue StandardError
1128
+ 0
1129
+ end
1130
+ end
1131
+
1132
+ def total_patients_on_arvs_and_ipt(patients_list, start_date, end_date)
1133
+ isoniazid_concept_id = concept('Isoniazid').concept_id
1134
+ pyridoxine_concept_id = concept('Pyridoxine').concept_id
1135
+
1136
+ patient_ids = []
1137
+ (patients_list || []).each do |row|
1138
+ patient_ids << row['patient_id'].to_i
1139
+ end
1140
+
1141
+ return [] if patient_ids.blank?
1142
+
1143
+ results = ActiveRecord::Base.connection.select_all(
1144
+ "SELECT ods.patient_id
1145
+ FROM orders ods
1146
+ INNER JOIN drug_order dos ON ods.order_id = dos.order_id AND ods.voided = 0 #{site_manager(operator: 'AND',
1147
+ column: 'dos.site_id', location: @location)}
1148
+ WHERE #{in_manager(column: 'ods.concept_id', values: [isoniazid_concept_id, pyridoxine_concept_id])}
1149
+ AND dos.quantity IS NOT NULL #{site_manager(operator: 'AND', column: 'ods.site_id', location: @location)}
1150
+ AND #{in_manager(column: 'ods.patient_id', values: patient_ids)}
1151
+ AND ods.start_date BETWEEN '#{start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
1152
+ AND '#{end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
1153
+ AND DATE(ods.start_date) = (SELECT MAX(DATE(o.start_date)) FROM orders o
1154
+ INNER JOIN drug_order d ON o.order_id = d.order_id AND o.voided = 0 #{site_manager(
1155
+ operator: 'AND', column: 'd.site_id', location: @location
1156
+ )}
1157
+ WHERE #{in_manager(column: 'o.concept_id',
1158
+ values: [
1159
+ isoniazid_concept_id, pyridoxine_concept_id
1160
+ ])}
1161
+ AND o.patient_id = ods.patient_id #{site_manager(operator: 'AND',
1162
+ column: 'o.site_id', location: @location)}
1163
+ AND d.quantity IS NOT NULL
1164
+ AND o.start_date BETWEEN '#{start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
1165
+ AND '#{end_date.to_date.strftime('%Y-%m-%d 23:59:59')}')
1166
+ GROUP BY ods.patient_id"
1167
+ )
1168
+
1169
+ ((results.count.to_f / patient_ids.count) * 100).to_i
1170
+ end
1171
+
1172
+ def total_patients_on_arvs_and_cpt(patients_list, start_date, end_date)
1173
+ cpt_concept_id = concept('Cotrimoxazole').concept_id
1174
+
1175
+ patient_ids = []
1176
+ (patients_list || []).each do |row|
1177
+ patient_ids << row['patient_id'].to_i
1178
+ end
1179
+
1180
+ return [] if patient_ids.blank?
1181
+
1182
+ results = ActiveRecord::Base.connection.select_all(
1183
+ "SELECT ods.patient_id
1184
+ FROM orders ods
1185
+ INNER JOIN drug_order dos ON ods.order_id = dos.order_id AND ods.voided = 0 #{site_manager(operator: 'AND',
1186
+ column: 'dos.site_id', location: @location)}
1187
+ WHERE ods.concept_id = #{cpt_concept_id}
1188
+ AND dos.quantity > 0 #{site_manager(operator: 'AND', column: 'ods.site_id', location: @location)}
1189
+ AND ods.patient_id in (#{patient_ids.join(',')})
1190
+ AND ods.start_date BETWEEN '#{start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
1191
+ AND '#{end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
1192
+ AND DATE(ods.start_date) = (SELECT MAX(DATE(o.start_date)) FROM orders o
1193
+ INNER JOIN drug_order d ON o.order_id = d.order_id AND o.voided = 0 #{site_manager(
1194
+ operator: 'AND', column: 'd.site_id', location: @location
1195
+ )}
1196
+ WHERE o.concept_id = #{cpt_concept_id} #{site_manager(operator: 'AND',
1197
+ column: 'o.site_id', location: @location)}
1198
+ AND d.quantity > 0
1199
+ AND o.patient_id = ods.patient_id
1200
+ AND o.start_date BETWEEN '#{start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
1201
+ AND '#{end_date.to_date.strftime('%Y-%m-%d 23:59:59')}')
1202
+
1203
+ GROUP BY ods.patient_id"
1204
+ )
1205
+
1206
+ ((results.count.to_f / patient_ids.count) * 100).to_i
1207
+ end
1208
+
1209
+ def total_breastfeeding_women(_patients_list, total_pregnant_women, _start_date, end_date)
1210
+ total_pregnant_women = if total_pregnant_women.empty?
1211
+ [0]
1212
+ else
1213
+ total_pregnant_women.map { |woman| woman['person_id'].to_i }
1214
+ end
1215
+
1216
+ encounter_types = ::EncounterType.where('LOWER(name) IN (?)',
1217
+ ['HIV CLINIC CONSULTATION'.downcase, 'HIV STAGING'.downcase])
1218
+ .select(:encounter_type_id)
1219
+
1220
+ breastfeeding_concepts = ::ConceptName.where('LOWER(name) IN (?)',
1221
+ ['Breast feeding?'.downcase, 'Breast feeding'.downcase,
1222
+ 'Breastfeeding'.downcase])
1223
+ .select(:concept_id)
1224
+
1225
+ ActiveRecord::Base.connection.select_all <<~SQL
1226
+ SELECT obs.person_id, obs.value_coded
1227
+ FROM obs
1228
+ INNER JOIN encounter enc
1229
+ ON enc.encounter_id = obs.encounter_id
1230
+ AND enc.voided = 0 #{site_manager(operator: 'AND', column: 'enc.site_id', location: @location)}
1231
+ AND enc.encounter_type IN (#{encounter_types.to_sql})
1232
+ INNER JOIN temp_earliest_start_date e
1233
+ ON e.patient_id = enc.patient_id #{site_manager(operator: 'AND', column: 'e.site_id', location: @location)}
1234
+ AND LEFT(e.gender, 1) = 'F'
1235
+ INNER JOIN temp_patient_outcomes
1236
+ ON temp_patient_outcomes.patient_id = e.patient_id #{site_manager(operator: 'AND', column: 'temp_patient_outcomes.site_id', location: @location)}
1237
+ AND temp_patient_outcomes.cum_outcome = 'On antiretrovirals'
1238
+ INNER JOIN (
1239
+ SELECT person_id, MAX(obs_datetime) AS obs_datetime
1240
+ FROM obs
1241
+ INNER JOIN encounter
1242
+ ON encounter.encounter_id = obs.encounter_id
1243
+ AND encounter.encounter_type IN (#{encounter_types.to_sql})
1244
+ AND encounter.voided = 0 #{site_manager(operator: 'AND', column: 'encounter.site_id', location: @location)}
1245
+ WHERE person_id IN (SELECT patient_id FROM temp_patient_outcomes WHERE cum_outcome = 'On antiretrovirals')
1246
+ AND concept_id IN (#{breastfeeding_concepts.to_sql})
1247
+ AND obs.voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1248
+ AND obs_datetime < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
1249
+ GROUP BY person_id
1250
+ ) AS max_obs
1251
+ ON max_obs.person_id = obs.person_id
1252
+ AND max_obs.obs_datetime = obs.obs_datetime
1253
+ WHERE obs.person_id = e.patient_id
1254
+ AND #{in_manager(column: 'obs.person_id', values: total_pregnant_women.join(','), negation: true)}
1255
+ AND obs.obs_datetime < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
1256
+ AND obs.concept_id IN (#{breastfeeding_concepts.to_sql})
1257
+ AND obs.voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1258
+ GROUP BY obs.person_id #{@adapter == 'mysql2' ? '' : ', obs.value_coded'}
1259
+ HAVING value_coded = 1065
1260
+ ORDER BY MAX(obs.obs_datetime) DESC;
1261
+ SQL
1262
+ end
1263
+
1264
+ def total_pregnant_women(_patients_list, _start_date, end_date)
1265
+ encounter_types = ::EncounterType.where('LOWER(name) IN (?)',
1266
+ ['HIV CLINIC CONSULTATION'.downcase, 'HIV STAGING'.downcase])
1267
+ .select(:encounter_type_id)
1268
+
1269
+ pregnant_concepts = ::ConceptName.where('LOWER(name) IN (?)',
1270
+ ['Is patient pregnant?'.downcase, 'patient pregnant'.downcase])
1271
+ .select(:concept_id)
1272
+
1273
+ ActiveRecord::Base.connection.select_all <<~SQL
1274
+ SELECT obs.person_id, obs.value_coded
1275
+ FROM obs obs
1276
+ INNER JOIN encounter enc
1277
+ ON enc.encounter_id = obs.encounter_id
1278
+ AND enc.voided = 0 #{site_manager(operator: 'AND', column: 'enc.site_id', location: @location)}
1279
+ AND enc.encounter_type IN (#{encounter_types.to_sql})
1280
+ INNER JOIN temp_earliest_start_date e
1281
+ ON e.patient_id = enc.patient_id #{site_manager(operator: 'AND', column: 'e.site_id', location: @location)}
1282
+ AND LEFT(e.gender, 1) = 'F'
1283
+ INNER JOIN temp_patient_outcomes
1284
+ ON temp_patient_outcomes.patient_id = e.patient_id #{site_manager(operator: 'AND', column: 'temp_patient_outcomes.site_id', location: @location)}
1285
+ AND temp_patient_outcomes.cum_outcome = 'On antiretrovirals'
1286
+ INNER JOIN (
1287
+ SELECT person_id, MAX(obs_datetime) AS obs_datetime
1288
+ FROM obs
1289
+ INNER JOIN encounter
1290
+ ON encounter.encounter_id = obs.encounter_id #{site_manager(operator: 'AND', column: 'encounter.site_id', location: @location)}
1291
+ AND encounter.encounter_type IN (#{encounter_types.to_sql})
1292
+ AND encounter.voided = 0
1293
+ WHERE concept_id IN (#{pregnant_concepts.to_sql})
1294
+ AND obs_datetime < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
1295
+ AND obs.voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1296
+ GROUP BY person_id
1297
+ ) AS max_obs
1298
+ ON max_obs.person_id = obs.person_id
1299
+ AND max_obs.obs_datetime = obs.obs_datetime
1300
+ WHERE obs.concept_id IN (#{pregnant_concepts.to_sql})
1301
+ AND obs.voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1302
+ GROUP BY obs.person_id #{@adapter == 'mysql2' ? '' : ', obs.value_coded'}
1303
+ HAVING obs.value_coded = 1065
1304
+ ORDER BY MAX(obs.obs_datetime) DESC;
1305
+ SQL
1306
+ end
1307
+
1308
+ def total_other_patients(patient_list, all_breastfeeding_women, all_pregnant_women)
1309
+ patient_ids = []
1310
+ all_pregnant_women_ids = []
1311
+ all_breastfeeding_women_ids = []
1312
+
1313
+ (patient_list || []).each do |row|
1314
+ patient_ids << row['patient_id'].to_i
1315
+ end
1316
+
1317
+ (all_pregnant_women || []).each do |row|
1318
+ all_pregnant_women_ids << row['person_id'].to_i
1319
+ end
1320
+
1321
+ (all_breastfeeding_women || []).each do |row|
1322
+ all_breastfeeding_women_ids << row['person_id'].to_i
1323
+ end
1324
+
1325
+ (patient_ids - (all_breastfeeding_women_ids + all_pregnant_women_ids))
1326
+ end
1327
+
1328
+ MIN_ART_ADHERENCE_THRESHOLD = 95.0 # Those below are not adherent
1329
+ MAX_ART_ADHERENCE_THRESHOLD = 105.0 # Thoseabove are not adherent
1330
+
1331
+ # Groups patients list into three groups based on the adherence rates
1332
+ #
1333
+ # Returns: A list of 3 lists as follows:
1334
+ # [
1335
+ # [adherent patients],
1336
+ # [inadherent patients],
1337
+ # [patients whose adherence rate is unknown]
1338
+ # ]
1339
+ def latest_art_adherence(patients_alive_and_on_art, _start_date, end_date)
1340
+ patients_alive_and_on_art = Set.new(patients_alive_and_on_art.map { |patient| patient['patient_id'] })
1341
+ end_date = ActiveRecord::Base.connection.quote(end_date)
1342
+
1343
+ not_adherent = ActiveRecord::Base.connection.select_all <<~SQL
1344
+ SELECT adherence.person_id
1345
+ FROM obs AS adherence
1346
+ INNER JOIN (
1347
+ SELECT obs.person_id, DATE(MAX(obs.obs_datetime)) AS visit_date
1348
+ FROM obs
1349
+ INNER JOIN orders
1350
+ ON orders.order_id = obs.order_id #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
1351
+ AND orders.concept_id IN (#{arv_drug_concepts.to_sql})
1352
+ AND orders.order_type_id = #{drug_order_type.order_type_id}
1353
+ AND orders.voided = 0
1354
+ INNER JOIN temp_patient_outcomes
1355
+ ON temp_patient_outcomes.patient_id = obs.person_id #{site_manager(operator: 'AND', column: 'temp_patient_outcomes.site_id', location: @location)}
1356
+ AND LOWER(temp_patient_outcomes.cum_outcome) = LOWER('On antiretrovirals')
1357
+ WHERE obs.concept_id = #{drug_order_adherence_concept.concept_id}
1358
+ AND obs.obs_datetime < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
1359
+ AND (obs.value_numeric IS NOT NULL OR obs.value_text IS NOT NULL)
1360
+ AND obs.voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1361
+ GROUP BY obs.person_id
1362
+ ) AS max_adherence
1363
+ ON max_adherence.person_id = adherence.person_id
1364
+ AND adherence.obs_datetime >= max_adherence.visit_date #{site_manager(operator: 'AND', column: 'adherence.site_id', location: @location)}
1365
+ AND adherence.obs_datetime < #{interval_manager(date: 'max_adherence.visit_date', value: 1, interval: 'DAY', operator: '+')}
1366
+ INNER JOIN orders
1367
+ ON orders.order_id = adherence.order_id
1368
+ AND orders.order_type_id = #{drug_order_type.order_type_id}
1369
+ AND orders.concept_id IN (#{arv_drug_concepts.to_sql})
1370
+ AND orders.voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
1371
+ WHERE adherence.concept_id = #{drug_order_adherence_concept.concept_id}
1372
+ AND (
1373
+ (adherence.value_numeric < #{MIN_ART_ADHERENCE_THRESHOLD} OR adherence.value_numeric > #{MAX_ART_ADHERENCE_THRESHOLD})
1374
+ OR
1375
+ (#{@adapter == 'mysql2' ? 'CAST(adherence.value_text AS SIGNED INTEGER)' : 'CAST(adherence.value_text AS INTEGER)'} < #{MIN_ART_ADHERENCE_THRESHOLD}
1376
+ OR #{@adapter == 'mysql2' ? 'CAST(adherence.value_text AS SIGNED INTEGER)' : 'CAST(adherence.value_text AS INTEGER)'} > #{MAX_ART_ADHERENCE_THRESHOLD})
1377
+ )
1378
+ AND adherence.voided = 0 #{site_manager(operator: 'AND', column: 'adherence.site_id', location: @location)}
1379
+ GROUP BY adherence.person_id
1380
+ SQL
1381
+
1382
+ not_adherent = not_adherent.empty? ? [] : not_adherent.map { |row| row['person_id'] }
1383
+
1384
+ adherent = ActiveRecord::Base.connection.select_all <<~SQL
1385
+ SELECT adherence.person_id
1386
+ FROM obs AS adherence
1387
+ INNER JOIN (
1388
+ SELECT obs.person_id, DATE(MAX(obs.obs_datetime)) AS visit_date
1389
+ FROM obs
1390
+ INNER JOIN orders
1391
+ ON orders.order_id = obs.order_id
1392
+ AND orders.concept_id IN (#{arv_drug_concepts.to_sql})
1393
+ AND orders.order_type_id = #{drug_order_type.order_type_id}
1394
+ AND orders.voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
1395
+ INNER JOIN temp_patient_outcomes
1396
+ ON temp_patient_outcomes.patient_id = obs.person_id #{site_manager(operator: 'AND', column: 'temp_patient_outcomes.site_id', location: @location)}
1397
+ AND #{in_manager(column: 'temp_patient_outcomes.patient_id', values: not_adherent.blank? ? [0] : not_adherent.join(','), negation: true)}
1398
+ AND LOWER(temp_patient_outcomes.cum_outcome) = LOWER('On antiretrovirals')
1399
+ WHERE obs.concept_id = #{drug_order_adherence_concept.concept_id}
1400
+ AND obs.obs_datetime < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
1401
+ AND (obs.value_numeric IS NOT NULL OR obs.value_text IS NOT NULL)
1402
+ AND obs.voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1403
+ GROUP BY obs.person_id
1404
+ ) AS max_adherence
1405
+ ON max_adherence.person_id = adherence.person_id
1406
+ AND adherence.obs_datetime >= max_adherence.visit_date #{site_manager(operator: 'AND', column: 'adherence.site_id', location: @location)}
1407
+ AND adherence.obs_datetime < #{interval_manager(date: 'max_adherence.visit_date', value: 1, interval: 'DAY', operator: '+')}
1408
+ INNER JOIN orders
1409
+ ON orders.order_id = adherence.order_id
1410
+ AND orders.order_type_id = #{drug_order_type.order_type_id}
1411
+ AND orders.concept_id IN (#{arv_drug_concepts.to_sql})
1412
+ AND orders.voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
1413
+ WHERE adherence.concept_id = #{drug_order_adherence_concept.concept_id}
1414
+ AND ((adherence.value_numeric >= #{MIN_ART_ADHERENCE_THRESHOLD}
1415
+ OR adherence.value_numeric <= #{MAX_ART_ADHERENCE_THRESHOLD})
1416
+ OR (#{@adapter == 'mysql2' ? 'CAST(adherence.value_text AS SIGNED INTEGER)' : 'CAST(adherence.value_text AS INTEGER)'} >= #{MIN_ART_ADHERENCE_THRESHOLD}
1417
+ OR #{@adapter == 'mysql2' ? 'CAST(adherence.value_text AS SIGNED INTEGER)' : 'CAST(adherence.value_text AS INTEGER)'} <= #{MAX_ART_ADHERENCE_THRESHOLD}))
1418
+ AND adherence.voided = 0 #{site_manager(operator: 'AND', column: 'adherence.site_id', location: @location)}
1419
+ GROUP BY adherence.person_id
1420
+ SQL
1421
+
1422
+ adherent = adherent.map { |row| row['person_id'] }
1423
+ unknown_adherence = patients_alive_and_on_art.to_a - adherent - not_adherent
1424
+ Rails.logger.info "Adherent: #{adherent.uniq.count}, Not Adherent: #{not_adherent.uniq.count}, Unknown: #{unknown_adherence.uniq.count}"
1425
+ [adherent.uniq, not_adherent.uniq, unknown_adherence]
1426
+ end
1427
+
1428
+ def adherence_encounter
1429
+ @adherence_encounter ||= encounter_type('ART ADHERENCE')
1430
+ end
1431
+
1432
+ def hiv_program
1433
+ @hiv_program ||= program('HIV PROGRAM')
1434
+ end
1435
+
1436
+ def drug_order_adherence_concept
1437
+ @drug_order_adherence_concept ||= concept('Drug order adherence')
1438
+ end
1439
+
1440
+ def drug_order_type
1441
+ @drug_order_type ||= order_type('Drug order')
1442
+ end
1443
+
1444
+ def arv_drug_concepts
1445
+ @arv_drug_concepts ||= ::ConceptSet.where(set: concept('Antiretroviral drugs'))
1446
+ .select(:concept_id)
1447
+ end
1448
+
1449
+ def write_tb_status_indicators(cohort_struct, _patients_alive_and_on_art, _start_date, end_date)
1450
+ cohort_struct.tb_suspected = []
1451
+ cohort_struct.tb_not_suspected = []
1452
+ cohort_struct.tb_confirmed_on_tb_treatment = []
1453
+ cohort_struct.tb_confirmed_currently_not_yet_on_tb_treatment = []
1454
+ cohort_struct.unknown_tb_status = []
1455
+
1456
+ tb_suspected_concept = concept('TB Suspected')
1457
+ tb_not_suspected_concept = concept('TB Not Suspected')
1458
+ tb_confirmed_but_not_on_treatment = concept('Confirmed TB NOT on Treatment')
1459
+ tb_confirmed_and_on_treatment = concept('Confirmed TB on Treatment')
1460
+
1461
+ # patients_alive_and_on_art
1462
+ (all_tb_statuses(end_date) || []).each do |data|
1463
+ tb_status_value = begin
1464
+ data['tb_status'].to_i
1465
+ rescue StandardError
1466
+ nil
1467
+ end
1468
+
1469
+ case tb_status_value
1470
+ when tb_suspected_concept.concept_id
1471
+ cohort_struct.tb_suspected << data['patient_id']
1472
+ when tb_not_suspected_concept.concept_id
1473
+ cohort_struct.tb_not_suspected << data['patient_id']
1474
+ when tb_confirmed_and_on_treatment.concept_id
1475
+ cohort_struct.tb_confirmed_on_tb_treatment << data['patient_id']
1476
+ when tb_confirmed_but_not_on_treatment.concept_id
1477
+ cohort_struct.tb_confirmed_currently_not_yet_on_tb_treatment << data['patient_id']
1478
+ else
1479
+ cohort_struct.unknown_tb_status << data['patient_id']
1480
+ end
1481
+ end
1482
+ end
1483
+
1484
+ def all_tb_statuses(end_date)
1485
+ ActiveRecord::Base.connection.select_all("
1486
+ SELECT e.*, tb_status
1487
+ FROM temp_earliest_start_date e
1488
+ LEFT JOIN temp_patient_tb_status s ON s.patient_id = e.patient_id #{site_manager(operator: 'AND',
1489
+ column: 's.site_id', location: @location)}
1490
+ INNER JOIN temp_patient_outcomes o ON o.patient_id = e.patient_id #{site_manager(operator: 'AND',
1491
+ column: 'o.site_id', location: @location)}
1492
+ WHERE LOWER(o.cum_outcome) = LOWER('On antiretrovirals') #{site_manager(operator: 'AND', column: 'e.site_id',
1493
+ location: @location)}
1494
+ AND DATE(e.date_enrolled) <= '#{end_date.to_date}';
1495
+ ")
1496
+ end
1497
+
1498
+ def get_tb_status(tb_status)
1499
+ registered = []
1500
+ (@tb_status || []).each do |status|
1501
+ if tb_status == status[:tb_status]
1502
+ registered << { patient_id: status[:patient_id], tb_status: status[:tb_status] }
1503
+ end
1504
+ end
1505
+
1506
+ registered
1507
+ end
1508
+
1509
+ def patients_side_effects_status(_patients_alive_and_on_art, end_date)
1510
+ with_side_effects = []
1511
+ without_side_effects = []
1512
+ unknowns = []
1513
+
1514
+ records = ActiveRecord::Base.connection.select_all <<~SQL
1515
+ SELECT e.*, s.has_se
1516
+ FROM temp_earliest_start_date e
1517
+ INNER JOIN temp_patient_side_effects s ON s.patient_id = e.patient_id #{site_manager(operator: 'AND', column: 's.site_id', location: @location)}
1518
+ INNER JOIN temp_patient_outcomes o ON o.patient_id = e.patient_id #{site_manager(operator: 'AND', column: 'o.site_id', location: @location)}
1519
+ WHERE LOWER(o.cum_outcome) = LOWER('On antiretrovirals') #{site_manager(operator: 'AND', column: 'e.site_id', location: @location)}
1520
+ AND DATE(e.date_enrolled) <= '#{end_date.to_date}';
1521
+ SQL
1522
+
1523
+ (records || []).each do |data|
1524
+ if data['has_se'] == 'Yes'
1525
+ with_side_effects << data['patient_id']
1526
+ elsif data['has_se'] == 'No'
1527
+ without_side_effects << data['patient_id']
1528
+ else
1529
+ unknowns << data['patient_id']
1530
+ end
1531
+ end
1532
+
1533
+ [with_side_effects, without_side_effects, unknowns]
1534
+ end
1535
+
1536
+ COHORT_REGIMENS = %w[
1537
+ 0P 2P 4PP 4PA 9PP 9PA 11PP 11PA 12PP 12PA 14PP 14PA 15PP 15PA 16P 17PP 17PA
1538
+ 4A 5A 6A 7A 8A 9A 10A 11A 12A 13A 14A 15A 16A 17A
1539
+ ].freeze
1540
+
1541
+ def cal_regimem_category(_patient_list, end_date)
1542
+ MalawiHivProgramReports::Cohort::Regimens.new.patient_regimens(end_date, @location).map do |prescription|
1543
+ regimen = prescription['regimen_category']&.upcase
1544
+
1545
+ regimen = 'unknown_regimen' if regimen == 'Unknown'.upcase || !COHORT_REGIMENS.include?(regimen)
1546
+
1547
+ {
1548
+ patient_id: prescription['patient_id'],
1549
+ regimen_category: regimen,
1550
+ drugs: prescription['drugs'].split(',').collect(&:to_i),
1551
+ prescription_date: prescription['prescription_date']
1552
+ }
1553
+ end
1554
+ end
1555
+
1556
+ def filter_prescriptions_by_regimen(prescriptions, regimen)
1557
+ prescriptions.select do |prescription|
1558
+ prescription[:regimen_category].casecmp?(regimen)
1559
+ end
1560
+ end
1561
+
1562
+ def filter_prescriptions_by_drugs(prescriptions, drug_ids)
1563
+ prescriptions.select do |prescription|
1564
+ prescription[:drugs].find { |drug_id| drug_ids.include?(drug_id) }
1565
+ end
1566
+ end
1567
+
1568
+ def died_in(month_str)
1569
+ registered = []
1570
+ if month_str == '4+ months'
1571
+ data = ActiveRecord::Base.connection.select_all(
1572
+ "SELECT t.patient_id, #{function_manager(function: 'died_in', location: @location,
1573
+ args: "t.patient_id, cum_outcome, earliest_start_date, #{@location}")} died_in
1574
+ FROM temp_patient_outcomes o
1575
+ INNER JOIN temp_earliest_start_date t ON t.patient_id = o.patient_id #{site_manager(operator: 'AND',
1576
+ column: 't.site_id', location: @location)}
1577
+ WHERE cum_outcome = 'Patient died' #{site_manager(operator: 'AND', column: 'o.site_id',
1578
+ location: @location)}
1579
+ GROUP BY t.patient_id #{@adapter == 'mysql2' ? '' : ', cum_outcome, earliest_start_date'}
1580
+ HAVING LOWER(#{if @adapter == 'mysql2'
1581
+ 'died_in'
1582
+ else
1583
+ function_manager(function: 'died_in', location: @location,
1584
+ args: "t.patient_id, cum_outcome, earliest_start_date, #{@location}")
1585
+ end}) IN (LOWER('4+ months'), LOWER('Unknown'))"
1586
+ )
1587
+ else
1588
+ data = ActiveRecord::Base.connection.select_all(
1589
+ "SELECT t.patient_id, #{function_manager(function: 'died_in', location: @location,
1590
+ args: "t.patient_id, cum_outcome, earliest_start_date, #{@location}")} died_in
1591
+ FROM temp_patient_outcomes o
1592
+ INNER JOIN temp_earliest_start_date t ON t.patient_id = o.patient_id #{site_manager(operator: 'AND',
1593
+ column: 't.site_id', location: @location)}
1594
+ WHERE cum_outcome = 'Patient died' #{site_manager(operator: 'AND', column: 'o.site_id',
1595
+ location: @location)}
1596
+ GROUP BY t.patient_id #{@adapter == 'mysql2' ? '' : ', cum_outcome, earliest_start_date'}
1597
+ HAVING LOWER(#{if @adapter == 'mysql2'
1598
+ 'died_in'
1599
+ else
1600
+ function_manager(function: 'died_in', location: @location,
1601
+ args: "t.patient_id, cum_outcome, earliest_start_date, #{@location}")
1602
+ end}) = '#{month_str.downcase}'"
1603
+ )
1604
+ end
1605
+
1606
+ (data || []).each do |patient|
1607
+ registered << patient['patient_id']
1608
+ end
1609
+
1610
+ registered
1611
+ end
1612
+
1613
+ def get_outcome(outcome)
1614
+ sql_patch = if outcome.casecmp('Pre-ART (Continue)').zero?
1615
+ "LOWER(cum_outcome) = '#{outcome.downcase}' OR LOWER(cum_outcome) = LOWER('Unknown')"
1616
+ else
1617
+ "LOWER(cum_outcome) = '#{outcome.downcase}'"
1618
+ end
1619
+
1620
+ ActiveRecord::Base.connection.select_all(
1621
+ "SELECT * FROM temp_patient_outcomes WHERE #{sql_patch} #{site_manager(operator: 'AND', column: 'site_id',
1622
+ location: @location)} GROUP BY patient_id"
1623
+ )
1624
+ end
1625
+
1626
+ def kaposis_sarcoma(start_date, end_date)
1627
+ # KAPOSIS SARCOMA
1628
+ concept_id = concept_name('KAPOSIS SARCOMA').concept_id
1629
+ yes_concept_id = concept_name('Yes').concept_id
1630
+ who_stages_criteria = concept_name('Who stages criteria present').concept_id
1631
+
1632
+ ActiveRecord::Base.connection.select_all <<~SQL
1633
+ SELECT t.patient_id
1634
+ FROM temp_earliest_start_date t
1635
+ INNER JOIN obs ON t.patient_id = obs.person_id #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)} AND obs.voided = 0
1636
+ WHERE t.date_enrolled BETWEEN '#{start_date}' AND '#{end_date}'
1637
+ AND ((obs.value_coded = #{concept_id} AND obs.concept_id = #{who_stages_criteria}) OR (obs.concept_id = #{concept_id} AND obs.value_coded = #{yes_concept_id}))
1638
+ AND DATE(obs_datetime) <= DATE(date_enrolled) #{site_manager(operator: 'AND', column: 't.site_id', location: @location)}
1639
+ GROUP BY patient_id
1640
+ SQL
1641
+ end
1642
+
1643
+ def current_episode_of_tb(start_date, end_date)
1644
+ # CURRENT EPISODE OF TB
1645
+ eptb_concept_id = concept('EXTRAPULMONARY TUBERCULOSIS (EPTB)').concept_id
1646
+ yes_concept_id = concept('Yes').concept_id
1647
+ pulmonary_tb_concept_id = concept('PULMONARY TUBERCULOSIS').concept_id
1648
+ current_ptb_concept_id = concept('PULMONARY TUBERCULOSIS (CURRENT)').concept_id
1649
+ who_stages_criteria = concept('Who stages criteria present').concept_id
1650
+
1651
+ ActiveRecord::Base.connection.select_all <<~SQL
1652
+ SELECT t.patient_id
1653
+ FROM temp_earliest_start_date t
1654
+ INNER JOIN obs ON t.patient_id = obs.person_id #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1655
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}'
1656
+ AND ( (#{in_manager(column: 'value_coded', values: [eptb_concept_id, pulmonary_tb_concept_id, current_ptb_concept_id])} AND concept_id = #{who_stages_criteria} )
1657
+ OR#{' '}
1658
+ (#{in_manager(column: 'concept_id', values: [eptb_concept_id, pulmonary_tb_concept_id, current_ptb_concept_id])} AND value_coded = #{yes_concept_id}))
1659
+ AND voided = 0 AND DATE(obs_datetime) <= DATE(date_enrolled) #{site_manager(operator: 'AND', column: 't.site_id', location: @location)}
1660
+ GROUP BY patient_id
1661
+ SQL
1662
+ end
1663
+
1664
+ def tb_within_the_last_two_years(patients_with_current_tb, start_date, end_date)
1665
+ # patients with current episode of tb
1666
+ patients_with_current_tb_episode = []
1667
+ (patients_with_current_tb || []).each do |patient|
1668
+ patients_with_current_tb_episode << patient['patient_id'].to_i
1669
+ end
1670
+
1671
+ patients_with_current_tb_episode = [0] if patients_with_current_tb_episode.blank?
1672
+
1673
+ # Pulmonary tuberculosis within the last 2 years
1674
+ pulmonary_tb_within_last_2yrs_concept_id = concept('Pulmonary tuberculosis within the last 2 years').concept_id
1675
+ ptb_within_the_past_two_yrs_concept_id = concept('Ptb within the past two years').concept_id
1676
+ who_stages_criteria = concept('Who stages criteria present').concept_id
1677
+ yes_concept_id = concept('Yes').concept_id
1678
+
1679
+ ActiveRecord::Base.connection.select_all(
1680
+ "SELECT t.patient_id
1681
+ FROM temp_earliest_start_date t
1682
+ INNER JOIN obs ON t.patient_id = obs.person_id #{site_manager(operator: 'AND', column: 'obs.site_id',
1683
+ location: @location)}
1684
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}'
1685
+ AND ((#{in_manager(column: 'value_coded',
1686
+ values: [pulmonary_tb_within_last_2yrs_concept_id,
1687
+ ptb_within_the_past_two_yrs_concept_id])}
1688
+ AND concept_id = #{who_stages_criteria})
1689
+ OR ( #{in_manager(column: 'concept_id',
1690
+ values: [pulmonary_tb_within_last_2yrs_concept_id,
1691
+ ptb_within_the_past_two_yrs_concept_id])} AND value_coded = #{yes_concept_id}))
1692
+ AND #{in_manager(column: 'patient_id', values: patients_with_current_tb_episode.join(','),
1693
+ negation: true)}
1694
+ AND voided = 0 AND DATE(obs_datetime) <= DATE(date_enrolled) #{site_manager(operator: 'AND',
1695
+ column: 't.site_id', location: @location)}
1696
+ GROUP BY patient_id"
1697
+ )
1698
+ end
1699
+
1700
+ def no_tb(total_registered, tb_within_the_last_two_years, current_episode_of_tb)
1701
+ total_registered_patients = []
1702
+ tb_within_2yrs_patients = []
1703
+ current_tb_episode_patients = []
1704
+
1705
+ (total_registered || []).each do |patient|
1706
+ total_registered_patients << patient['patient_id'].to_i
1707
+ end
1708
+
1709
+ (tb_within_the_last_two_years || []).each do |patient|
1710
+ tb_within_2yrs_patients << patient['patient_id'].to_i
1711
+ end
1712
+
1713
+ (current_episode_of_tb || []).each do |patient|
1714
+ current_tb_episode_patients << patient['patient_id'].to_i
1715
+ end
1716
+
1717
+ total_registered_patients - (tb_within_2yrs_patients + current_tb_episode_patients)
1718
+ end
1719
+
1720
+ def cum_no_tb(cum_total_registered, cum_tb_within_the_last_two_years, cum_current_episode_of_tb)
1721
+ total_registered_patients = []
1722
+ tb_within_2yrs_patients = []
1723
+ current_tb_episode_patients = []
1724
+
1725
+ (cum_total_registered || []).each do |patient|
1726
+ total_registered_patients << patient['patient_id'].to_i
1727
+ end
1728
+
1729
+ (cum_tb_within_the_last_two_years || []).each do |patient|
1730
+ tb_within_2yrs_patients << patient['patient_id'].to_i
1731
+ end
1732
+
1733
+ (cum_current_episode_of_tb || []).each do |patient|
1734
+ current_tb_episode_patients << patient['patient_id'].to_i
1735
+ end
1736
+
1737
+ total_registered_patients - (tb_within_2yrs_patients + current_tb_episode_patients)
1738
+ end
1739
+
1740
+ def children_12_59_months(start_date, end_date)
1741
+ concept = ::ConceptName.where('LOWER(name) IN (?)', 'HIV Infected'.downcase).select(:concept_id)
1742
+
1743
+ find_patients_by_reason_for_starting(start_date, end_date, concept)
1744
+ end
1745
+
1746
+ def unknown_other_reason_outside_guidelines(start_date, end_date)
1747
+ # All WHO stage 1 and 2 patients that were enrolled before '2016-04-01'
1748
+ # should be included in this group.
1749
+ unknown_concepts = ::ConceptName.where('LOWER(name) IN (?)', ['Unknown'.downcase, 'None'.downcase])
1750
+ .select(:concept_id)
1751
+ .to_sql
1752
+
1753
+ if start_date.to_date > '2016-04-01'.to_date
1754
+ return ActiveRecord::Base.connection.select_all <<~SQL
1755
+ SELECT patient_id
1756
+ FROM temp_earliest_start_date
1757
+ WHERE reason_for_starting_art IN (#{unknown_concepts})
1758
+ AND date_enrolled >= '#{start_date}'
1759
+ AND date_enrolled <= '#{end_date}' #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
1760
+ SQL
1761
+ end
1762
+
1763
+ stage_1_and_2_concept_names = ['LYMPHOCYTE COUNT BELOW THRESHOLD WITH WHO STAGE 1'.downcase,
1764
+ 'LYMPHOCYTES'.downcase,
1765
+ 'LYMPHOCYTE COUNT BELOW THRESHOLD WITH WHO STAGE 2'.downcase,
1766
+ 'WHO stage I adult'.downcase,
1767
+ 'WHO stage I peds'.downcase,
1768
+ 'WHO STAGE 1'.downcase,
1769
+ 'WHO stage II adult'.downcase,
1770
+ 'WHO stage II peds'.downcase,
1771
+ 'WHO STAGE 2'.downcase]
1772
+
1773
+ stage_1_and_2_concepts = ::ConceptName.where('LOWER(name) IN (?)', stage_1_and_2_concept_names)
1774
+ .select('DISTINCT concept_id')
1775
+ .to_sql
1776
+
1777
+ ActiveRecord::Base.connection.select_all <<~SQL
1778
+ SELECT patient_id
1779
+ FROM temp_earliest_start_date
1780
+ WHERE
1781
+ (
1782
+ reason_for_starting_art IN (#{unknown_concepts})
1783
+ AND date_enrolled >= '#{start_date}'
1784
+ AND date_enrolled <= '#{end_date}'
1785
+ )
1786
+ OR (
1787
+ reason_for_starting_art IN (#{stage_1_and_2_concepts})
1788
+ AND date_enrolled <= DATE('2016-04-01')
1789
+ ) #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
1790
+ SQL
1791
+ end
1792
+
1793
+ def who_stage_four(start_date, end_date)
1794
+ concepts = ::ConceptName.where('LOWER(name) IN (?)',
1795
+ ['WHO stage IV adult'.downcase, 'WHO stage IV peds'.downcase,
1796
+ 'WHO STAGE 4'.downcase])
1797
+ .select(:concept_id)
1798
+ find_patients_by_reason_for_starting(start_date, end_date, concepts)
1799
+ end
1800
+
1801
+ def who_stage_three(start_date, end_date)
1802
+ concepts = ::ConceptName.where('LOWER(name) IN (?)',
1803
+ ['WHO stage III adult'.downcase, 'WHO stage III peds'.downcase,
1804
+ 'WHO STAGE 3'.downcase])
1805
+ .select(:concept_id)
1806
+ find_patients_by_reason_for_starting(start_date, end_date, concepts)
1807
+ end
1808
+
1809
+ def pregnant_women(start_date, end_date)
1810
+ concepts = ::ConceptName.where('LOWER(name) IN (?)', ['PATIENT PREGNANT'.downcase, 'Is patient pregnant at initiation?'.downcase,
1811
+ 'Patient pregnant state'.downcase, 'Is patient pregnant?'.downcase])
1812
+ .select(:concept_id)
1813
+ find_patients_by_reason_for_starting(start_date, end_date, concepts)
1814
+ end
1815
+
1816
+ def breastfeeding_mothers(start_date, end_date)
1817
+ concept = ::ConceptName.where('LOWER(name) IN (?)', 'Breastfeeding'.downcase).select(:concept_id)
1818
+ find_patients_by_reason_for_starting(start_date, end_date, concept)
1819
+ end
1820
+
1821
+ def asymptomatic(start_date, end_date)
1822
+ # for WHO stage 1 and 2 to be included in asymptomatic, the patients are supposed to
1823
+ # be enrolled on HIV PROGRAM after 2016-04-01
1824
+ patients = []
1825
+
1826
+ asymptomatic_concepts = ::ConceptName.where('LOWER(name) IN (?)',
1827
+ ['ASYMPTOMATIC'.downcase, 'Asymptomatic HIV infection'.downcase])
1828
+ .select(:concept_id)
1829
+ find_patients_by_reason_for_starting(start_date, end_date, asymptomatic_concepts)
1830
+ .each { |patient| patients << patient['patient_id'] }
1831
+
1832
+ reason_concepts = ::ConceptName.where(name: ['WHO stage I adult'.downcase,
1833
+ 'WHO stage I peds'.downcase,
1834
+ 'WHO stage 1'.downcase,
1835
+ 'WHO stage II adult'.downcase,
1836
+ 'WHO stage II peds'.downcase,
1837
+ 'LYMPHOCYTE COUNT BELOW THRESHOLD WITH WHO STAGE 1'.downcase,
1838
+ 'LYMPHOCYTES'.downcase,
1839
+ 'LYMPHOCYTE COUNT BELOW THRESHOLD WITH WHO STAGE 2'.downcase])
1840
+ .select(:concept_id)
1841
+
1842
+ revised_art_guidelines_date = '2016-04-01'.to_date
1843
+ start_date = revised_art_guidelines_date if start_date.to_date < revised_art_guidelines_date
1844
+
1845
+ find_patients_by_reason_for_starting(start_date, end_date, reason_concepts)
1846
+ .each { |patient| patients << patient['patient_id'] }
1847
+
1848
+ patients
1849
+ end
1850
+
1851
+ def who_stage_two(start_date, end_date)
1852
+ concepts = ::ConceptName.where('LOWER(name) IN (?)', ['CD4 COUNT LESS THAN OR EQUAL TO 750'.downcase,
1853
+ 'CD4 count less than or equal to 500'.downcase,
1854
+ 'CD4 COUNT LESS THAN OR EQUAL TO 350'.downcase,
1855
+ 'CD4 count less than 350'.downcase,
1856
+ 'CD4 count less than 250'.downcase,
1857
+ 'CD4 COUNT LESS THAN OR EQUAL TO 250'.downcase])
1858
+ .select(:concept_id)
1859
+ find_patients_by_reason_for_starting(start_date, end_date, concepts)
1860
+ end
1861
+
1862
+ def confirmed_hiv_infection_in_infants_pcr(start_date, end_date)
1863
+ concept = ::ConceptName.where('LOWER(name) IN (?)', 'HIV PCR'.downcase).select(:concept_id)
1864
+ find_patients_by_reason_for_starting(start_date, end_date, concept)
1865
+ end
1866
+
1867
+ def presumed_severe_hiv_disease_in_infants(start_date, end_date)
1868
+ concepts = ::ConceptName.where('LOWER(name) IN (?)', ['PRESUMED SEVERE HIV'.downcase,
1869
+ 'PRESUMED SEVERE HIV CRITERIA IN INFANTS'.downcase])
1870
+ .select(:concept_id)
1871
+ find_patients_by_reason_for_starting(start_date, end_date, concepts)
1872
+ end
1873
+
1874
+ def find_patients_by_reason_for_starting(start_date, end_date, reason_concept_ids)
1875
+ ActiveRecord::Base.connection.select_all <<~SQL
1876
+ SELECT patient_id
1877
+ FROM temp_earliest_start_date
1878
+ WHERE date_enrolled >= '#{start_date}'
1879
+ AND date_enrolled <= '#{end_date}'
1880
+ AND reason_for_starting_art IN (#{reason_concept_ids.to_sql}) #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
1881
+ SQL
1882
+ end
1883
+
1884
+ def unknown_age(start_date, end_date)
1885
+ ActiveRecord::Base.connection.select_all(
1886
+ "SELECT patient_id
1887
+ FROM temp_earliest_start_date
1888
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}' #{site_manager(operator: 'AND',
1889
+ column: 'site_id', location: @location)}
1890
+ AND (age_at_initiation IS NULL OR age_at_initiation < 0 OR birthdate IS NULL)
1891
+ GROUP BY patient_id"
1892
+ )
1893
+ end
1894
+
1895
+ def unknown_gender(start_date, end_date)
1896
+ ActiveRecord::Base.connection.select_all(
1897
+ "SELECT patient_id
1898
+ FROM temp_earliest_start_date
1899
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}' #{site_manager(operator: 'AND',
1900
+ column: 'site_id', location: @location)}
1901
+ AND gender IS NULL OR LENGTH(gender) < 1 GROUP BY patient_id;"
1902
+ )
1903
+ end
1904
+
1905
+ def adults_at_art_initiation(start_date, end_date)
1906
+ ActiveRecord::Base.connection.select_all(
1907
+ "SELECT patient_id FROM temp_earliest_start_date
1908
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}' #{site_manager(operator: 'AND',
1909
+ column: 'site_id', location: @location)}
1910
+ AND age_at_initiation > 14 GROUP BY patient_id"
1911
+ )
1912
+ end
1913
+
1914
+ def children_24_months_14_years_at_art_initiation(start_date, end_date)
1915
+ ActiveRecord::Base.connection.select_all(
1916
+ "SELECT patient_id FROM temp_earliest_start_date
1917
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}' #{site_manager(operator: 'AND',
1918
+ column: 'site_id', location: @location)}
1919
+ AND age_at_initiation BETWEEN 2 AND 14 GROUP BY patient_id"
1920
+ )
1921
+ end
1922
+
1923
+ def children_below_24_months_at_art_initiation(start_date, end_date)
1924
+ ActiveRecord::Base.connection.select_all(
1925
+ "SELECT patient_id FROM temp_earliest_start_date
1926
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}' #{site_manager(operator: 'AND',
1927
+ column: 'site_id', location: @location)}
1928
+ AND (age_at_initiation >= 0 AND age_at_initiation < 2) GROUP BY patient_id"
1929
+ )
1930
+ end
1931
+
1932
+ def non_pregnant_females(start_date, end_date, pregnant_women = [])
1933
+ pregnant_women_ids = []
1934
+ (pregnant_women || []).each do |patient|
1935
+ pregnant_women_ids << patient
1936
+ end
1937
+ pregnant_women_ids = [0] if pregnant_women_ids.blank?
1938
+
1939
+ ActiveRecord::Base.connection.select_all <<~SQL
1940
+ SELECT t.patient_id
1941
+ FROM temp_earliest_start_date t
1942
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}' #{site_manager(operator: 'AND', column: 't.site_id', location: @location)}
1943
+ AND (t.gender = 'F' OR t.gender = 'Female')
1944
+ AND t.patient_id NOT IN(#{pregnant_women_ids.join(',')})
1945
+ GROUP BY t.patient_id
1946
+ SQL
1947
+ end
1948
+
1949
+ def pregnant_females_all_ages(start_date, end_date)
1950
+ yes_concept_id = concept('Yes').concept_id
1951
+ preg_concept_id = concept('IS PATIENT PREGNANT?').concept_id
1952
+ patient_preg_concept_id = concept('PATIENT PREGNANT').concept_id
1953
+ preg_at_initiation_concept_id = concept('PREGNANT AT INITIATION?').concept_id
1954
+ reason_for_starting_concept_id = concept('Reason for ART eligibility').concept_id
1955
+
1956
+ # (patient_id_plus_date_enrolled || []).each do |patient_id, date_enrolled|
1957
+ registered = ActiveRecord::Base.connection.select_all <<~SQL
1958
+ SELECT patients.*, obs.value_coded
1959
+ FROM temp_earliest_start_date AS patients
1960
+ INNER JOIN obs
1961
+ ON obs.person_id = patients.patient_id
1962
+ AND #{in_manager(column: 'obs.concept_id', values: [preg_concept_id, patient_preg_concept_id, preg_at_initiation_concept_id, reason_for_starting_concept_id])}
1963
+ AND obs.obs_datetime >= patients.earliest_start_date
1964
+ AND obs.obs_datetime < #{interval_manager(date: 'patients.earliest_start_date', value: 1, interval: 'DAY', operator: '+')}
1965
+ AND #{in_manager(column: 'obs.value_coded', values: [yes_concept_id, patient_preg_concept_id])}
1966
+ AND obs.voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1967
+ WHERE patients.gender IN ('F','Female')
1968
+ AND patients.date_enrolled BETWEEN '#{start_date}' AND '#{end_date}' #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
1969
+ GROUP BY patient_id #{@adapter == 'mysql2' ? '' : ',obs.value_coded'}
1970
+ SQL
1971
+
1972
+ pregnant_at_initiation = ActiveRecord::Base.connection.select_all <<~SQL
1973
+ SELECT patient_id, #{function_manager(function: 'patient_reason_for_starting_art', location: @location,
1974
+ args: "patient_id, #{@location}")} reason_concept_id
1975
+ FROM temp_earliest_start_date
1976
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}'
1977
+ AND gender IN ('F','Female') #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
1978
+ GROUP BY patient_id
1979
+ HAVING #{if @adapter == 'mysql2'
1980
+ 'reason_concept_id IN (1755, 7972, 6131)'
1981
+ else
1982
+ in_manager(column: function_manager(function: 'patient_reason_for_starting_art', location: @location, args: "patient_id, #{@location}").to_s,
1983
+ values: [
1984
+ 1755, 7972, 6131
1985
+ ])
1986
+ end};
1987
+ SQL
1988
+ pregnant_at_initiation_ids = []
1989
+ (pregnant_at_initiation || []).each do |patient|
1990
+ pregnant_at_initiation_ids << patient['patient_id'].to_i
1991
+ end
1992
+
1993
+ pregnant_at_initiation_ids = [0] if pregnant_at_initiation_ids.blank?
1994
+
1995
+ transfer_ins_women = ActiveRecord::Base.connection.select_all <<~SQL
1996
+ SELECT patient_id, #{function_manager(function: 're_initiated_check', location: @location,
1997
+ args: "patient_id, date_enrolled, #{@location}")} re_initiated
1998
+ FROM temp_earliest_start_date
1999
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}'
2000
+ AND DATE(date_enrolled) != DATE(earliest_start_date)
2001
+ AND gender IN ('F','Female') #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
2002
+ AND #{in_manager(column: 'patient_id', values: pregnant_at_initiation_ids)}
2003
+ GROUP BY patient_id
2004
+ HAVING #{ if @adapter == 'mysql2'
2005
+ 're_initiated'
2006
+ else
2007
+ function_manager(function: 're_initiated_check', location: @location,
2008
+ args: "patient_id, date_enrolled, #{@location}")
2009
+ end} != 'Re-initiated'
2010
+ SQL
2011
+
2012
+ transfer_ins_preg_women = []
2013
+ all_pregnant_females = []
2014
+ (transfer_ins_women || []).each do |patient|
2015
+ transfer_ins_preg_women << patient['patient_id'].to_i if patient['patient_id'].to_i != 0
2016
+ end
2017
+
2018
+ (registered || []).each do |patient|
2019
+ all_pregnant_females << patient['patient_id'].to_i if patient['patient_id'].to_i != 0
2020
+ end
2021
+
2022
+ (all_pregnant_females + transfer_ins_preg_women).uniq
2023
+ end
2024
+
2025
+ def initial_females_all_ages(start_date, end_date, data)
2026
+ clients = []
2027
+ women = ActiveRecord::Base.connection.select_all("
2028
+ SELECT * FROM temp_earliest_start_date e
2029
+ WHERE patient_id IN(#{data.length.positive? ? data.join(',') : 0}) #{site_manager(operator: 'AND',
2030
+ column: 'e.site_id', location: @location)}
2031
+ AND date_enrolled BETWEEN '#{start_date.to_date}' AND '#{end_date.to_date}'
2032
+ AND DATE(date_enrolled) = DATE(earliest_start_date);")
2033
+
2034
+ (women || []).each do |w|
2035
+ clients << w
2036
+ end
2037
+
2038
+ clients
2039
+ end
2040
+
2041
+ def males(start_date, end_date)
2042
+ ActiveRecord::Base.connection.select_all(
2043
+ "SELECT * FROM temp_earliest_start_date t
2044
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}' #{site_manager(operator: 'AND',
2045
+ column: 'site_id', location: @location)}
2046
+ AND (gender = 'Male' OR gender = 'M') GROUP BY patient_id"
2047
+ )
2048
+ end
2049
+
2050
+ def transfer_in(start_date, end_date, re_initiated_on_art)
2051
+ start_date = ActiveRecord::Base.connection.quote(start_date)
2052
+ end_date = ActiveRecord::Base.connection.quote(end_date)
2053
+
2054
+ re_initiated_on_art = re_initiated_on_art.empty? ? [0] : re_initiated_on_art.rows.collect(&:first)
2055
+
2056
+ ActiveRecord::Base.connection.select_all <<~SQL
2057
+ SELECT temp_earliest_start_date.patient_id
2058
+ FROM temp_earliest_start_date
2059
+ INNER JOIN clinic_registration_encounter
2060
+ ON clinic_registration_encounter.patient_id = temp_earliest_start_date.patient_id #{site_manager(operator: 'AND', column: 'clinic_registration_encounter.site_id', location: @location)}
2061
+ LEFT JOIN ever_registered_obs
2062
+ ON ever_registered_obs.person_id = temp_earliest_start_date.patient_id #{site_manager(operator: 'AND', column: 'ever_registered_obs.site_id', location: @location)}
2063
+ AND ever_registered_obs.value_coded = (
2064
+ SELECT concept_id FROM concept_name WHERE LOWER(name) = LOWER('Yes') AND voided = 0 LIMIT 1
2065
+ )
2066
+ LEFT JOIN (
2067
+ SELECT person_id, MIN(obs_datetime) AS obs_datetime
2068
+ FROM ever_registered_obs #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)}
2069
+ GROUP BY person_id
2070
+ ) AS max_ever_registered_obs
2071
+ ON max_ever_registered_obs.person_id = ever_registered_obs.person_id
2072
+ AND max_ever_registered_obs.obs_datetime = ever_registered_obs.obs_datetime
2073
+ LEFT JOIN obs AS last_taken_art_obs
2074
+ ON last_taken_art_obs.encounter_id = ever_registered_obs.encounter_id
2075
+ AND last_taken_art_obs.voided = 0 #{site_manager(operator: 'AND', column: 'last_taken_art_obs.site_id', location: @location)}
2076
+ AND last_taken_art_obs.concept_id = (
2077
+ SELECT concept_id FROM concept_name WHERE LOWER(name) = LOWER('DATE ART LAST TAKEN') LIMIT 1
2078
+ )
2079
+ WHERE (date_enrolled BETWEEN #{start_date} AND #{end_date})
2080
+ AND date_enrolled != earliest_start_date #{site_manager(operator: 'AND', column: 'temp_earliest_start_date.site_id', location: @location)}
2081
+ AND COALESCE(#{
2082
+ if @adapter == 'mysql2'
2083
+ <<~SQL
2084
+ TIMESTAMPDIFF(day,last_taken_art_obs.value_datetime,last_taken_art_obs.obs_datetime) <= 14
2085
+ SQL
2086
+ else
2087
+ <<~SQL
2088
+ EXTRACT(DAY FROM AGE(last_taken_art_obs.obs_datetime::DATE, last_taken_art_obs.value_datetime::DATE)) <= 14
2089
+ SQL
2090
+ end
2091
+ }, TRUE)
2092
+ AND #{in_manager(column: 'temp_earliest_start_date.patient_id', values: re_initiated_on_art, negation: true)}
2093
+ GROUP BY temp_earliest_start_date.patient_id;
2094
+ SQL
2095
+ end
2096
+
2097
+ def re_initiated_on_art(start_date, end_date)
2098
+ ActiveRecord::Base.connection.select_all <<~SQL
2099
+ SELECT temp_earliest_start_date.patient_id
2100
+ FROM temp_earliest_start_date
2101
+ INNER JOIN clinic_registration_encounter
2102
+ ON temp_earliest_start_date.patient_id = clinic_registration_encounter.patient_id #{site_manager(operator: 'AND', column: 'clinic_registration_encounter.site_id', location: @location)}
2103
+ INNER JOIN ever_registered_obs
2104
+ ON clinic_registration_encounter.encounter_id = ever_registered_obs.encounter_id
2105
+ AND ever_registered_obs.value_coded = (SELECT concept_id FROM concept_name
2106
+ WHERE LOWER(name) = LOWER('Yes') AND voided = 0 LIMIT 1) #{site_manager(operator: 'AND', column: 'ever_registered_obs.site_id', location: @location)}
2107
+ INNER JOIN (
2108
+ SELECT person_id, MIN(obs_datetime) AS obs_datetime
2109
+ FROM ever_registered_obs #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)}
2110
+ GROUP BY person_id
2111
+ ) AS max_ever_registered_obs
2112
+ ON max_ever_registered_obs.person_id = ever_registered_obs.person_id
2113
+ AND max_ever_registered_obs.obs_datetime = ever_registered_obs.obs_datetime
2114
+ INNER JOIN obs AS last_taken_art_obs
2115
+ ON last_taken_art_obs.encounter_id = clinic_registration_encounter.encounter_id
2116
+ AND last_taken_art_obs.voided = 0 #{site_manager(operator: 'AND', column: 'last_taken_art_obs.site_id', location: @location)}
2117
+ AND last_taken_art_obs.concept_id = (
2118
+ SELECT concept_id FROM concept_name WHERE LOWER(name) = LOWER('DATE ART LAST TAKEN') LIMIT 1
2119
+ )
2120
+ WHERE (date_enrolled BETWEEN '#{start_date}' AND '#{end_date}')
2121
+ AND #{timestampdiff_manager(date1: 'last_taken_art_obs.value_datetime', date2: 'last_taken_art_obs.obs_datetime', interval: 'day')} > 14
2122
+ AND date_enrolled != earliest_start_date #{site_manager(operator: 'AND', column: 'temp_earliest_start_date.site_id', location: @location)}
2123
+ GROUP BY temp_earliest_start_date.patient_id;
2124
+ SQL
2125
+ end
2126
+
2127
+ def initiated_on_art_first_time(start_date, end_date)
2128
+ ActiveRecord::Base.connection.select_all(
2129
+ "SELECT * FROM temp_earliest_start_date
2130
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}'
2131
+ AND date_enrolled = earliest_start_date #{site_manager(operator: 'AND', column: 'site_id',
2132
+ location: @location)}
2133
+ GROUP BY patient_id"
2134
+ )
2135
+ end
2136
+
2137
+ def males_initiated_on_art_first_time(start_date, end_date, data)
2138
+ clients = []
2139
+ (data || []).each do |e|
2140
+ gender = e['gender']&.upcase&.first
2141
+ next if gender.blank?
2142
+ next unless gender == 'M'
2143
+
2144
+ date_enrolled = e['date_enrolled'].to_date
2145
+ start_date = start_date.to_date
2146
+ end_date = end_date.to_date
2147
+ date_enrolled >= start_date && date_enrolled <= end_date ? clients << e : next
2148
+ end
2149
+
2150
+ clients
2151
+ end
2152
+
2153
+ def get_cum_start_date
2154
+ cum_start_date = ActiveRecord::Base.connection.select_value <<~SQL
2155
+ SELECT MIN(date_enrolled) FROM temp_earliest_start_date #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)}
2156
+ SQL
2157
+
2158
+ begin
2159
+ cum_start_date.to_date
2160
+ rescue StandardError
2161
+ nil
2162
+ end
2163
+ end
2164
+
2165
+ def total_registered(start_date, end_date)
2166
+ ActiveRecord::Base.connection.select_all(
2167
+ "SELECT * FROM temp_earliest_start_date
2168
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}' #{site_manager(operator: 'AND',
2169
+ column: 'site_id', location: @location)}
2170
+ GROUP BY patient_id"
2171
+ )
2172
+ end
2173
+
2174
+ def load_tmp_patient_table(_cohort_struct)
2175
+ create_tmp_patient_table
2176
+ # arv_orders.each_with_object({}) do |order, patient_tab|
2177
+ # next if patient_tab.include?(order.patient_id)
2178
+ #
2179
+ # person = ::Person.find(order.patient_id)
2180
+ # next unless person.birthdate # && patient_in_program?(person.patient)
2181
+ #
2182
+ # add_patient_record(person, order, cohort_struct)
2183
+ #
2184
+ # patient_tab[order.patient_id] = person
2185
+ # end
2186
+ end
2187
+
2188
+ def create_tmp_patient_table_2(_end_date)
2189
+ ##########################################################
2190
+ ActiveRecord::Base.connection.execute <<~SQL
2191
+ DROP FUNCTION IF EXISTS patient_date_enrolled;
2192
+ SQL
2193
+
2194
+ ::Drug.arv_drugs.map(&:concept_id)
2195
+
2196
+ ActiveRecord::Base.connection.execute <<~SQL
2197
+ CREATE FUNCTION patient_date_enrolled(my_patient_id int) RETURNS DATE
2198
+ DETERMINISTIC
2199
+ BEGIN
2200
+ DECLARE my_start_date DATE;
2201
+ DECLARE min_start_date DATETIME;
2202
+ DECLARE arv_concept_id INT(11);
2203
+
2204
+ SET arv_concept_id = (SELECT concept_id FROM concept_name WHERE name ='ANTIRETROVIRAL DRUGS' LIMIT 1);
2205
+
2206
+ SET my_start_date = (SELECT DATE(o.start_date) FROM drug_order d INNER JOIN orders o ON d.order_id = o.order_id AND o.voided = 0 WHERE o.patient_id = my_patient_id AND drug_inventory_id IN(SELECT drug_id FROM drug WHERE concept_id IN(SELECT concept_id FROM concept_set WHERE concept_set = arv_concept_id)) AND d.quantity > 0 AND o.start_date = (SELECT min(start_date) FROM drug_order d INNER JOIN orders o ON d.order_id = o.order_id AND o.voided = 0 WHERE d.quantity > 0 AND o.patient_id = my_patient_id AND drug_inventory_id IN(SELECT drug_id FROM drug WHERE concept_id IN(SELECT concept_id FROM concept_set WHERE concept_set = arv_concept_id))) LIMIT 1);
2207
+
2208
+
2209
+ RETURN my_start_date;
2210
+ END;
2211
+ SQL
2212
+ ##########################################################
2213
+
2214
+ ActiveRecord::Base.connection.execute <<~SQL
2215
+ DROP TABLE IF EXISTS `temp_earliest_start_date`;
2216
+ SQL
2217
+
2218
+ ActiveRecord::Base.connection.execute <<~SQL
2219
+ CREATE TABLE temp_earliest_start_date
2220
+ select
2221
+ `p`.`patient_id` AS `patient_id`,
2222
+ `pe`.`gender` AS `gender`,
2223
+ `pe`.`birthdate`,
2224
+ date_antiretrovirals_started(`p`.`patient_id`, min(`s`.`start_date`)) AS `earliest_start_date`,
2225
+ cast(patient_date_enrolled(`p`.`patient_id`) as date) AS `date_enrolled`,
2226
+ `person`.`death_date` AS `death_date`,
2227
+ (select timestampdiff(year, `pe`.`birthdate`, min(`s`.`start_date`))) AS `age_at_initiation`,
2228
+ (select timestampdiff(day, `pe`.`birthdate`, min(`s`.`start_date`))) AS `age_in_days`
2229
+ from
2230
+ ((`patient_program` `p`
2231
+ left join `person` `pe` ON ((`pe`.`person_id` = `p`.`patient_id`))
2232
+ left join `patient_state` `s` ON ((`p`.`patient_program_id` = `s`.`patient_program_id`)))
2233
+ left join `person` ON ((`person`.`person_id` = `p`.`patient_id`)))
2234
+ where
2235
+ ((`p`.`voided` = 0)
2236
+ and (`s`.`voided` = 0)
2237
+ and (`p`.`program_id` = 1)
2238
+ and (`s`.`state` = 7))
2239
+ group by `p`.`patient_id`;
2240
+ SQL
2241
+ end
2242
+
2243
+ def arv_orders
2244
+ ::Order.joins(:drug_order).where(
2245
+ 'drug_order.drug_inventory_id in (?)', ::Drug.arv_drugs.collect(&:drug_id)
2246
+ ).order(:start_date)
2247
+ end
2248
+
2249
+ def add_patient_record(person, order, cohort_struct)
2250
+ date_enrolled = order.start_date.to_date
2251
+ art_earliest_start_date = patient_earliest_start_date(order.patient_id, date_enrolled)
2252
+
2253
+ if date_enrolled == art_earliest_start_date
2254
+ cohort_struct.cum_initiated_on_art_first_time ||= 0
2255
+ cohort_struct.cum_initiated_on_art_first_time += 1
2256
+ end
2257
+
2258
+ age_in_months_when_starting = (art_earliest_start_date - person.birthdate).to_i
2259
+ age_when_starting = (age_in_months_when_starting / 365).to_i
2260
+ deathdate = person.death_date ? "'#{person.death_date.to_date}'" : 'NULL'
2261
+
2262
+ ActiveRecord::Base.connection.execute(
2263
+ "INSERT INTO temp_earliest_start_date (
2264
+ patient_id,
2265
+ date_enrolled,
2266
+ earliest_start_date,
2267
+ gender,
2268
+ birthdate,
2269
+ birthdate_estimated,
2270
+ death_date,
2271
+ age_at_initiation,
2272
+ age_in_days
2273
+ ) VALUES (
2274
+ #{order.patient_id},
2275
+ '#{order.start_date.to_date}',
2276
+ '#{art_earliest_start_date.to_date}',
2277
+ '#{person.gender}',
2278
+ '#{person.birthdate}',
2279
+ '#{person.birthdate_estimated}',
2280
+ #{deathdate},
2281
+ '#{age_when_starting}',
2282
+ '#{age_in_months_when_starting}'
2283
+ )"
2284
+ )
2285
+ end
2286
+
2287
+ # Retrieve the earliest (clinic?) start date for a patient
2288
+ def patient_earliest_start_date(patient_id, min_start_date)
2289
+ result = ActiveRecord::Base.connection.select_one(
2290
+ "SELECT date_antiretrovirals_started(
2291
+ #{patient_id}, '#{min_start_date.to_date}'
2292
+ ) AS date"
2293
+ )
2294
+ result['date'].to_date
2295
+ end
2296
+
2297
+ # Returns a list of reasons for starting ART for each patient.
2298
+ def patients_art_start_reason(patient_ids)
2299
+ ActiveRecord::Base.connection.execute(
2300
+ "SELECT person_id as patient_id, name, obs_datetime
2301
+ FROM reason_for_art_eligibility_obs
2302
+ WHERE #{in_manager(column: 'person_id',
2303
+ values: patient_ids)} #{site_manager(operator: 'AND',
2304
+ column: 'reason_for_art_eligibility_obs.site_id', location: @location)}"
2305
+ )
2306
+ end
2307
+
2308
+ def patient_death_date(patient)
2309
+ ::PatientState.find_by(program: program('HIV PROGRAM'), patient:)
2310
+ end
2311
+
2312
+ # Filter out patients with given start causes from patient_ids
2313
+ def filter_patients_with_start_cause(patient_ids, start_cause_concept_ids)
2314
+ obs_concepts = start_cause_concept_ids.push(concept('WHO STAGES CRITERIA PRESENT').concept_id)
2315
+ obs_values = start_cause_concept_ids.push(concept('YES').concept_id)
2316
+
2317
+ ActiveRecord::Base.connection.execute(
2318
+ "SELECT *
2319
+ FROM hiv_staging_conditions_obs
2320
+ WHERE #{in_manager(column: 'concept_id', values: obs_concepts)}
2321
+ AND #{in_manager(column: 'value_coded', values: obs_values)}
2322
+ AND #{in_manager(column: 'person_id',
2323
+ values: patient_ids)} #{site_manager(operator: 'AND',
2324
+ column: 'hiv_staging_conditions_obs.site_id', location: @location)}
2325
+ GROUP BY person_id"
2326
+ )
2327
+ end
2328
+
2329
+ def patient_in_program?(patient)
2330
+ return false unless patient
2331
+
2332
+ pprogram = ::PatientProgram.find_by(program: program('HIV PROGRAM'), patient:)
2333
+ return false unless pprogram
2334
+
2335
+ ::PatientState.where(patient_program: pprogram, state: 7).exists?
2336
+ end
2337
+ end
2338
+ end
2339
+ end
2340
+ # rubocop:enable Metrics/ClassLength