malawi_hiv_program_reports 1.0.1 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/app/services/malawi_hiv_program_reports/README.md +16 -0
  3. data/app/services/malawi_hiv_program_reports/adapters/moh/custom.rb +199 -0
  4. data/app/services/malawi_hiv_program_reports/archiving_candidates.rb +130 -0
  5. data/app/services/malawi_hiv_program_reports/arv_refill_periods.rb +311 -0
  6. data/app/services/malawi_hiv_program_reports/clinic/README.md +5 -0
  7. data/app/services/malawi_hiv_program_reports/clinic/appointments_report.rb +317 -0
  8. data/app/services/malawi_hiv_program_reports/clinic/discrepancy_report.rb +42 -0
  9. data/app/services/malawi_hiv_program_reports/clinic/docs/hypertension_report.md +31 -0
  10. data/app/services/malawi_hiv_program_reports/clinic/drug_dispensations.rb +48 -0
  11. data/app/services/malawi_hiv_program_reports/clinic/external_consultation_clients.rb +69 -0
  12. data/app/services/malawi_hiv_program_reports/clinic/hypertension_report.rb +223 -0
  13. data/app/services/malawi_hiv_program_reports/clinic/ipt_coverage.rb +112 -0
  14. data/app/services/malawi_hiv_program_reports/clinic/ipt_report.rb +69 -0
  15. data/app/services/malawi_hiv_program_reports/clinic/lims_results.rb +55 -0
  16. data/app/services/malawi_hiv_program_reports/clinic/outcome_list.rb +127 -0
  17. data/app/services/malawi_hiv_program_reports/clinic/patients_alive_and_on_treatment.rb +57 -0
  18. data/app/services/malawi_hiv_program_reports/clinic/patients_due_for_viral_load.rb +39 -0
  19. data/app/services/malawi_hiv_program_reports/clinic/patients_on_antiretrovirals.rb +44 -0
  20. data/app/services/malawi_hiv_program_reports/clinic/patients_on_dtg.rb +36 -0
  21. data/app/services/malawi_hiv_program_reports/clinic/patients_on_treatment.rb +42 -0
  22. data/app/services/malawi_hiv_program_reports/clinic/patients_with_outdated_demographics.rb +173 -0
  23. data/app/services/malawi_hiv_program_reports/clinic/pregnant_patients.rb +91 -0
  24. data/app/services/malawi_hiv_program_reports/clinic/regimen_dispensation_data.rb +282 -0
  25. data/app/services/malawi_hiv_program_reports/clinic/regimen_switch.rb +456 -0
  26. data/app/services/malawi_hiv_program_reports/clinic/regimens_and_formulations.rb +182 -0
  27. data/app/services/malawi_hiv_program_reports/clinic/regimens_by_weight_and_gender.rb +108 -0
  28. data/app/services/malawi_hiv_program_reports/clinic/retention.rb +246 -0
  29. data/app/services/malawi_hiv_program_reports/clinic/stock_card_report.rb +65 -0
  30. data/app/services/malawi_hiv_program_reports/clinic/tpt_outcome.rb +494 -0
  31. data/app/services/malawi_hiv_program_reports/clinic/tx_rtt.rb +169 -0
  32. data/app/services/malawi_hiv_program_reports/clinic/viral_load.rb +292 -0
  33. data/app/services/malawi_hiv_program_reports/clinic/viral_load_disaggregated.rb +97 -0
  34. data/app/services/malawi_hiv_program_reports/clinic/viral_load_results.rb +175 -0
  35. data/app/services/malawi_hiv_program_reports/clinic/visits_report.rb +113 -0
  36. data/app/services/malawi_hiv_program_reports/clinic/vl_collection.rb +48 -0
  37. data/app/services/malawi_hiv_program_reports/cohort/outcomes.rb +338 -0
  38. data/app/services/malawi_hiv_program_reports/cohort/regimens.rb +69 -0
  39. data/app/services/malawi_hiv_program_reports/cohort/side_effects.rb +141 -0
  40. data/app/services/malawi_hiv_program_reports/cohort/tpt.rb +172 -0
  41. data/app/services/malawi_hiv_program_reports/moh/cohort.rb +278 -0
  42. data/app/services/malawi_hiv_program_reports/moh/cohort_builder.rb +2337 -0
  43. data/app/services/malawi_hiv_program_reports/moh/cohort_disaggregated.rb +608 -0
  44. data/app/services/malawi_hiv_program_reports/moh/cohort_disaggregated_additions.rb +208 -0
  45. data/app/services/malawi_hiv_program_reports/moh/cohort_disaggregated_builder.rb +526 -0
  46. data/app/services/malawi_hiv_program_reports/moh/cohort_struct.rb +219 -0
  47. data/app/services/malawi_hiv_program_reports/moh/cohort_survival_analysis.rb +203 -0
  48. data/app/services/malawi_hiv_program_reports/moh/moh_tpt.rb +223 -0
  49. data/app/services/malawi_hiv_program_reports/moh/tpt_newly_initiated.rb +235 -0
  50. data/app/services/malawi_hiv_program_reports/pepfar/defaulter_list.rb +25 -0
  51. data/app/services/malawi_hiv_program_reports/pepfar/maternal_status.rb +29 -0
  52. data/app/services/malawi_hiv_program_reports/pepfar/patient_start_vl.rb +45 -0
  53. data/app/services/malawi_hiv_program_reports/pepfar/regimen_switch.rb +479 -0
  54. data/app/services/malawi_hiv_program_reports/pepfar/sc_arvdisp.rb +174 -0
  55. data/app/services/malawi_hiv_program_reports/pepfar/sc_curr.rb +98 -0
  56. data/app/services/malawi_hiv_program_reports/pepfar/tb_prev.rb +163 -0
  57. data/app/services/malawi_hiv_program_reports/pepfar/tb_prev2.rb +222 -0
  58. data/app/services/malawi_hiv_program_reports/pepfar/tb_prev3.rb +421 -0
  59. data/app/services/malawi_hiv_program_reports/pepfar/tpt_status.rb +181 -0
  60. data/app/services/malawi_hiv_program_reports/pepfar/tx_ml.rb +181 -0
  61. data/app/services/malawi_hiv_program_reports/pepfar/tx_new.rb +205 -0
  62. data/app/services/malawi_hiv_program_reports/pepfar/tx_rtt.rb +288 -0
  63. data/app/services/malawi_hiv_program_reports/pepfar/tx_tb.rb +283 -0
  64. data/app/services/malawi_hiv_program_reports/pepfar/utils.rb +141 -0
  65. data/app/services/malawi_hiv_program_reports/pepfar/viral_load_coverage.rb +414 -0
  66. data/app/services/malawi_hiv_program_reports/pepfar/viral_load_coverage2.rb +433 -0
  67. data/app/services/malawi_hiv_program_reports/report_map.rb +56 -0
  68. data/app/services/malawi_hiv_program_reports/utils/README.md +8 -0
  69. data/app/services/malawi_hiv_program_reports/utils/common_sql_query_utils.rb +60 -0
  70. data/app/services/malawi_hiv_program_reports/utils/concurrency_utils.rb +53 -0
  71. data/app/services/malawi_hiv_program_reports/utils/docs/common_sql_query_utils.md +53 -0
  72. data/app/services/malawi_hiv_program_reports/utils/model_utils.rb +66 -0
  73. data/app/services/malawi_hiv_program_reports/utils/parameter_utils.rb +32 -0
  74. data/app/services/malawi_hiv_program_reports/utils/time_utils.rb +52 -0
  75. data/lib/malawi_hiv_program_reports/version.rb +1 -1
  76. metadata +74 -1
@@ -0,0 +1,2337 @@
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 #{site_manager(operator: 'AND', column: 'pp.site_id', location: @location)}
883
+ 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)}
884
+ AND o.value_coded = #{new_patient_concept}
885
+ AND o.voided = 0 AND o.obs_datetime < DATE(#{end_date}) + INTERVAL 1 DAY
886
+ WHERE pp.program_id = 1 #{site_manager(operator: 'AND', column: 'pp.site_id', location: @location)}
887
+ GROUP BY pp.patient_id #{@adapter == 'mysql2' ? '' : ', pp.site_id'}
888
+ SQL
889
+ end
890
+
891
+ def drop_temp_register_start_date_table
892
+ ActiveRecord::Base.connection.execute <<~SQL
893
+ DROP TABLE IF EXISTS temp_register_start_date
894
+ SQL
895
+ end
896
+
897
+ def remove_drug_refills_and_external_consultation(end_date)
898
+ ActiveRecord::Base.connection.execute <<~SQL
899
+ DELETE FROM temp_cohort_members
900
+ WHERE #{in_manager(column: 'patient_id', values: drug_refills_and_external_consultation_list(end_date))}
901
+ SQL
902
+ end
903
+
904
+ # this just gives all clients who are truly external or drug refill
905
+ # rubocop:disable Metrics/MethodLength
906
+ # rubocop:disable Metrics/AbcSize
907
+ def drug_refills_and_external_consultation_list(end_date)
908
+ to_remove = [0]
909
+
910
+ type_of_patient_concept = concept('Type of patient').concept_id
911
+ new_patient_concept = concept('New patient').concept_id
912
+ drug_refill_concept = concept('Drug refill').concept_id
913
+ external_concept = concept('External consultation').concept_id
914
+ hiv_clinic_registration_id = ::EncounterType.find_by_name('HIV CLINIC REGISTRATION').encounter_type_id
915
+
916
+ ActiveRecord::Base.connection.select_all("SELECT e.patient_id
917
+ FROM temp_cohort_members e
918
+ LEFT JOIN encounter as hiv_registration ON hiv_registration.patient_id = e.patient_id
919
+ AND hiv_registration.encounter_datetime < DATE(#{end_date})
920
+ AND hiv_registration.encounter_type = #{hiv_clinic_registration_id}
921
+ AND hiv_registration.voided = 0 #{site_manager(operator: 'AND', column: 'hiv_registration.site_id',
922
+ location: @location)}
923
+ 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(
924
+ date: end_date, value: 1, interval: 'DAY', operator: '+'
925
+ )} #{site_manager(operator: 'AND', column: 'site_id',
926
+ location: @location)}) AS new_patient ON e.patient_id = new_patient.person_id
927
+ 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(
928
+ date: end_date, value: 1, interval: 'DAY', operator: '+'
929
+ )} #{site_manager(operator: 'AND', column: 'site_id',
930
+ location: @location)}) AS refill ON e.patient_id = refill.person_id
931
+ 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(
932
+ date: end_date, value: 1, interval: 'DAY', operator: '+'
933
+ )} #{site_manager(operator: 'AND', column: 'site_id',
934
+ location: @location)}) AS external ON e.patient_id = external.person_id
935
+ WHERE (refill.value_coded IS NOT NULL OR external.value_coded IS NOT NULL)
936
+ AND NOT (hiv_registration.encounter_id IS NOT NULL OR new_patient.value_coded IS NOT NULL)
937
+ GROUP BY e.patient_id
938
+ ORDER BY MAX(hiv_registration.encounter_datetime) DESC, MAX(refill.obs_datetime) DESC, MAX(external.obs_datetime) DESC;").each do |record|
939
+ to_remove << record['patient_id'].to_i
940
+ end
941
+ to_remove.join(',')
942
+ end
943
+ # rubocop:enable Metrics/MethodLength
944
+ # rubocop:enable Metrics/AbcSize
945
+
946
+ def create_tmp_patient_table
947
+ ActiveRecord::Base.connection.execute('DROP TABLE IF EXISTS temp_earliest_start_date')
948
+ exe_tmp_patient_table(adapter: @adapter)
949
+
950
+ ActiveRecord::Base.connection.execute(
951
+ 'CREATE INDEX patient_id_index ON temp_earliest_start_date (patient_id)'
952
+ )
953
+ ActiveRecord::Base.connection.execute(
954
+ 'CREATE INDEX site_id_tesd ON temp_earliest_start_date (site_id)'
955
+ )
956
+ ActiveRecord::Base.connection.execute(
957
+ 'CREATE INDEX patient_id__site_id_index ON temp_earliest_start_date (patient_id, site_id)'
958
+ )
959
+ ActiveRecord::Base.connection.execute(
960
+ 'CREATE INDEX date_enrolled_index ON temp_earliest_start_date (date_enrolled)'
961
+ )
962
+ ActiveRecord::Base.connection.execute(
963
+ 'CREATE INDEX patient_id__date_enrolled_index ON temp_earliest_start_date (patient_id, date_enrolled)'
964
+ )
965
+ ActiveRecord::Base.connection.execute(
966
+ 'CREATE INDEX earliest_start_date_index ON temp_earliest_start_date (earliest_start_date)'
967
+ )
968
+ ActiveRecord::Base.connection.execute(
969
+ 'CREATE INDEX earliest_start_date__date_enrolled_index ON temp_earliest_start_date (patient_id, earliest_start_date, date_enrolled, gender)'
970
+ )
971
+ ActiveRecord::Base.connection.execute(
972
+ 'CREATE INDEX idx_reason_for_art ON temp_earliest_start_date (reason_for_starting_art)'
973
+ )
974
+ ActiveRecord::Base.connection.execute(
975
+ 'CREATE INDEX birthdate_idx ON temp_earliest_start_date (birthdate)'
976
+ )
977
+ end
978
+
979
+ def update_cum_outcome(end_date)
980
+ MalawiHivProgramReports::Cohort::Outcomes.new(end_date:, definition: @outcomes_definition, location: @location)
981
+ .update_cummulative_outcomes
982
+ end
983
+
984
+ def update_tb_status(end_date)
985
+ ActiveRecord::Base.connection.execute(
986
+ 'DROP TABLE IF EXISTS temp_patient_tb_status'
987
+ )
988
+
989
+ ActiveRecord::Base.connection.execute <<~SQL
990
+ CREATE TABLE temp_patient_tb_status (
991
+ patient_id INT PRIMARY KEY,
992
+ site_id INT DEFAULT #{@location},
993
+ tb_status INT
994
+ )
995
+ SQL
996
+
997
+ ActiveRecord::Base.connection.execute('CREATE INDEX tb_status_index ON temp_patient_tb_status (tb_status)')
998
+ ActiveRecord::Base.connection.execute('CREATE INDEX site_id_tpts ON temp_patient_tb_status (site_id)')
999
+ ActiveRecord::Base.connection.execute('CREATE INDEX patient_id_site_id_index ON temp_patient_tb_status (patient_id, site_id)')
1000
+ ActiveRecord::Base.connection.execute('CREATE INDEX patient_id_tb_status_index ON temp_patient_tb_status (patient_id, tb_status)')
1001
+
1002
+ ActiveRecord::Base.connection.execute <<~SQL
1003
+ INSERT INTO temp_patient_tb_status (patient_id, tb_status)
1004
+ SELECT e.patient_id, obs.value_coded
1005
+ FROM temp_earliest_start_date e
1006
+ INNER JOIN temp_patient_outcomes o ON o.patient_id = e.patient_id #{site_manager(operator: 'AND', column: 'o.site_id', location: @location)}
1007
+ RIGHT JOIN obs ON obs.person_id = o.patient_id #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1008
+ WHERE e.date_enrolled <= '#{end_date}' AND obs.obs_datetime <= '#{end_date} 23:59:59'
1009
+ AND LOWER(cum_outcome) = LOWER('On antiretrovirals') AND obs.voided = 0
1010
+ AND obs.concept_id = 7459 #{site_manager(operator: 'AND', column: 'e.site_id', location: @location)}
1011
+ AND obs.obs_datetime = (
1012
+ SELECT MAX(t.obs_datetime) FROM obs t WHERE t.concept_id = 7459 AND t.voided = 0
1013
+ 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)}
1014
+ ) GROUP BY e.patient_id #{@adapter == 'mysql2' ? '' : ', obs.value_coded'}
1015
+ SQL
1016
+ end
1017
+
1018
+ def update_patient_side_effects(end_date)
1019
+ MalawiHivProgramReports::Cohort::SideEffects.new.update_side_effects(end_date, @location)
1020
+ end
1021
+
1022
+ private
1023
+
1024
+ def total_patients_with_screened_bp(total_alive_and_on_art, _start_date, end_date)
1025
+ return 0 if total_alive_and_on_art.blank? || total_alive_and_on_art.empty?
1026
+
1027
+ bp_concepts = ::ConceptName.where(name: ['Systolic blood pressure', 'Diastolic blood pressure'])
1028
+ .select(:concept_id)
1029
+
1030
+ results = ActiveRecord::Base.connection.select_all <<~SQL
1031
+ SELECT o.person_id
1032
+ FROM obs o
1033
+ INNER JOIN (
1034
+ SELECT person_id, MAX(obs.obs_datetime) AS obs_datetime
1035
+ FROM obs
1036
+ WHERE voided = 0 AND concept_id IN (#{bp_concepts.to_sql})
1037
+ AND (value_text IS NOT NULL OR value_numeric IS NOT NULL)
1038
+ 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: '-')}
1039
+ AND #{in_manager(column: 'person_id', values: total_alive_and_on_art)} #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1040
+ GROUP BY person_id
1041
+ ) AS max_obs
1042
+ ON max_obs.person_id = o.person_id
1043
+ AND max_obs.obs_datetime = o.obs_datetime
1044
+ WHERE o.voided = 0 #{site_manager(operator: 'AND', column: 'o.site_id', location: @location)}
1045
+ AND o.concept_id IN (#{bp_concepts.to_sql})
1046
+ AND (o.value_text IS NOT NULL OR o.value_numeric IS NOT NULL)
1047
+ GROUP BY o.person_id
1048
+ SQL
1049
+
1050
+ ((results.count.to_f / total_alive_and_on_art.count) * 100).to_i
1051
+ end
1052
+
1053
+ def total_patients_alive_and_on_art_above_30_years(total_alive_and_on_art, end_date)
1054
+ return nil if total_alive_and_on_art.blank?
1055
+ return nil if total_alive_and_on_art.empty?
1056
+
1057
+ results = ActiveRecord::Base.connection.select_all <<~SQL
1058
+ 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"}
1059
+ FROM temp_earliest_start_date tesd
1060
+ WHERE #{in_manager(column: 'tesd.patient_id', values: total_alive_and_on_art.map { |r| r['patient_id'].to_i }.join(','))}
1061
+ GROUP BY tesd.patient_id
1062
+ HAVING #{@adapter == 'mysql2' ? 'age' : "(EXTRACT(YEAR FROM AGE(DATE('#{end_date}'), tesd.birthdate)))"} >= 30
1063
+ SQL
1064
+
1065
+ # map the results to patient ids
1066
+ results&.map { |r| r['patient_id'].to_i }
1067
+ end
1068
+
1069
+ def total_patients_on_family_planning(patients_list, start_date, end_date)
1070
+ patient_ids = []
1071
+ patient_list = []
1072
+
1073
+ (patients_list || []).each do |row|
1074
+ patient_ids << row['patient_id'].to_i
1075
+ end
1076
+
1077
+ return [] if patient_ids.blank?
1078
+
1079
+ all_women = ActiveRecord::Base.connection.select_all(
1080
+ "SELECT * FROM temp_earliest_start_date
1081
+ WHERE gender IN ('F','Female') AND patient_id IN (#{patient_ids.join(',')})
1082
+ AND date_enrolled BETWEEN '#{start_date.to_date}' AND '#{end_date.to_date}' #{site_manager(operator: 'AND',
1083
+ column: 'site_id', location: @location)}
1084
+ GROUP BY patient_id"
1085
+ )
1086
+
1087
+ (all_women || []).each do |patient|
1088
+ patient_list << patient['patient_id'].to_i
1089
+ end
1090
+
1091
+ return 0 if patient_list.blank?
1092
+
1093
+ hiv_clinic_consultation_encounter_type_id = encounter_type('HIV CLINIC CONSULTATION').encounter_type_id
1094
+ method_of_family_planning_concept_id = concept('Method of family planning').concept_id
1095
+ family_planning_action_to_take_concept_id = concept('Family planning, action to take').concept_id
1096
+ none_concept_id = [concept('None').concept_id, concept('No').concept_id]
1097
+
1098
+ results = ActiveRecord::Base.connection.select_all(
1099
+ "SELECT o.person_id
1100
+ FROM obs o
1101
+ INNER JOIN encounter e on e.encounter_id = o.encounter_id AND e.encounter_type = #{hiv_clinic_consultation_encounter_type_id} #{site_manager(
1102
+ operator: 'AND', column: 'e.site_id', location: @location
1103
+ )}
1104
+ WHERE o.voided = 0 AND e.voided = 0 #{site_manager(operator: 'AND', column: 'o.site_id', location: @location)}
1105
+ AND #{in_manager(column: 'o.concept_id',
1106
+ values: [family_planning_action_to_take_concept_id,
1107
+ method_of_family_planning_concept_id])}
1108
+ AND #{in_manager(column: 'o.value_coded', values: none_concept_id.join(','), negation: true)}
1109
+ AND #{in_manager(column: 'o.person_id', values: patient_list.join(','))}
1110
+ AND o.obs_datetime BETWEEN '#{start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
1111
+ AND '#{end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
1112
+ AND DATE(o.obs_datetime) = (SELECT max(date(obs.obs_datetime)) FROM obs obs
1113
+ WHERE obs.voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1114
+ AND #{in_manager(column: 'obs.concept_id',
1115
+ values: [family_planning_action_to_take_concept_id,
1116
+ method_of_family_planning_concept_id])}
1117
+ 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')}'
1118
+ AND obs.person_id = o.person_id) #{site_manager(operator: 'AND', column: 'o.site_id', location: @location)}
1119
+ GROUP BY o.person_id"
1120
+ )
1121
+
1122
+ begin
1123
+ ((results.count.to_f / patient_list.count) * 100).to_i
1124
+ rescue StandardError
1125
+ 0
1126
+ end
1127
+ end
1128
+
1129
+ def total_patients_on_arvs_and_ipt(patients_list, start_date, end_date)
1130
+ isoniazid_concept_id = concept('Isoniazid').concept_id
1131
+ pyridoxine_concept_id = concept('Pyridoxine').concept_id
1132
+
1133
+ patient_ids = []
1134
+ (patients_list || []).each do |row|
1135
+ patient_ids << row['patient_id'].to_i
1136
+ end
1137
+
1138
+ return [] if patient_ids.blank?
1139
+
1140
+ results = ActiveRecord::Base.connection.select_all(
1141
+ "SELECT ods.patient_id
1142
+ FROM orders ods
1143
+ INNER JOIN drug_order dos ON ods.order_id = dos.order_id AND ods.voided = 0 #{site_manager(operator: 'AND',
1144
+ column: 'dos.site_id', location: @location)}
1145
+ WHERE #{in_manager(column: 'ods.concept_id', values: [isoniazid_concept_id, pyridoxine_concept_id])}
1146
+ AND dos.quantity IS NOT NULL #{site_manager(operator: 'AND', column: 'ods.site_id', location: @location)}
1147
+ AND #{in_manager(column: 'ods.patient_id', values: patient_ids)}
1148
+ AND ods.start_date BETWEEN '#{start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
1149
+ AND '#{end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
1150
+ AND DATE(ods.start_date) = (SELECT MAX(DATE(o.start_date)) FROM orders o
1151
+ INNER JOIN drug_order d ON o.order_id = d.order_id AND o.voided = 0 #{site_manager(
1152
+ operator: 'AND', column: 'd.site_id', location: @location
1153
+ )}
1154
+ WHERE #{in_manager(column: 'o.concept_id',
1155
+ values: [
1156
+ isoniazid_concept_id, pyridoxine_concept_id
1157
+ ])}
1158
+ AND o.patient_id = ods.patient_id #{site_manager(operator: 'AND',
1159
+ column: 'o.site_id', location: @location)}
1160
+ AND d.quantity IS NOT NULL
1161
+ AND o.start_date BETWEEN '#{start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
1162
+ AND '#{end_date.to_date.strftime('%Y-%m-%d 23:59:59')}')
1163
+ GROUP BY ods.patient_id"
1164
+ )
1165
+
1166
+ ((results.count.to_f / patient_ids.count) * 100).to_i
1167
+ end
1168
+
1169
+ def total_patients_on_arvs_and_cpt(patients_list, start_date, end_date)
1170
+ cpt_concept_id = concept('Cotrimoxazole').concept_id
1171
+
1172
+ patient_ids = []
1173
+ (patients_list || []).each do |row|
1174
+ patient_ids << row['patient_id'].to_i
1175
+ end
1176
+
1177
+ return [] if patient_ids.blank?
1178
+
1179
+ results = ActiveRecord::Base.connection.select_all(
1180
+ "SELECT ods.patient_id
1181
+ FROM orders ods
1182
+ INNER JOIN drug_order dos ON ods.order_id = dos.order_id AND ods.voided = 0 #{site_manager(operator: 'AND',
1183
+ column: 'dos.site_id', location: @location)}
1184
+ WHERE ods.concept_id = #{cpt_concept_id}
1185
+ AND dos.quantity > 0 #{site_manager(operator: 'AND', column: 'ods.site_id', location: @location)}
1186
+ AND ods.patient_id in (#{patient_ids.join(',')})
1187
+ AND ods.start_date BETWEEN '#{start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
1188
+ AND '#{end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
1189
+ AND DATE(ods.start_date) = (SELECT MAX(DATE(o.start_date)) FROM orders o
1190
+ INNER JOIN drug_order d ON o.order_id = d.order_id AND o.voided = 0 #{site_manager(
1191
+ operator: 'AND', column: 'd.site_id', location: @location
1192
+ )}
1193
+ WHERE o.concept_id = #{cpt_concept_id} #{site_manager(operator: 'AND',
1194
+ column: 'o.site_id', location: @location)}
1195
+ AND d.quantity > 0
1196
+ AND o.patient_id = ods.patient_id
1197
+ AND o.start_date BETWEEN '#{start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
1198
+ AND '#{end_date.to_date.strftime('%Y-%m-%d 23:59:59')}')
1199
+
1200
+ GROUP BY ods.patient_id"
1201
+ )
1202
+
1203
+ ((results.count.to_f / patient_ids.count) * 100).to_i
1204
+ end
1205
+
1206
+ def total_breastfeeding_women(_patients_list, total_pregnant_women, _start_date, end_date)
1207
+ total_pregnant_women = if total_pregnant_women.empty?
1208
+ [0]
1209
+ else
1210
+ total_pregnant_women.map { |woman| woman['person_id'].to_i }
1211
+ end
1212
+
1213
+ encounter_types = ::EncounterType.where('LOWER(name) IN (?)',
1214
+ ['HIV CLINIC CONSULTATION'.downcase, 'HIV STAGING'.downcase])
1215
+ .select(:encounter_type_id)
1216
+
1217
+ breastfeeding_concepts = ::ConceptName.where('LOWER(name) IN (?)',
1218
+ ['Breast feeding?'.downcase, 'Breast feeding'.downcase,
1219
+ 'Breastfeeding'.downcase])
1220
+ .select(:concept_id)
1221
+
1222
+ ActiveRecord::Base.connection.select_all <<~SQL
1223
+ SELECT obs.person_id, obs.value_coded
1224
+ FROM obs
1225
+ INNER JOIN encounter enc
1226
+ ON enc.encounter_id = obs.encounter_id
1227
+ AND enc.voided = 0 #{site_manager(operator: 'AND', column: 'enc.site_id', location: @location)}
1228
+ AND enc.encounter_type IN (#{encounter_types.to_sql})
1229
+ INNER JOIN temp_earliest_start_date e
1230
+ ON e.patient_id = enc.patient_id #{site_manager(operator: 'AND', column: 'e.site_id', location: @location)}
1231
+ AND LEFT(e.gender, 1) = 'F'
1232
+ INNER JOIN temp_patient_outcomes
1233
+ ON temp_patient_outcomes.patient_id = e.patient_id #{site_manager(operator: 'AND', column: 'temp_patient_outcomes.site_id', location: @location)}
1234
+ AND temp_patient_outcomes.cum_outcome = 'On antiretrovirals'
1235
+ INNER JOIN (
1236
+ SELECT person_id, MAX(obs_datetime) AS obs_datetime
1237
+ FROM obs
1238
+ INNER JOIN encounter
1239
+ ON encounter.encounter_id = obs.encounter_id
1240
+ AND encounter.encounter_type IN (#{encounter_types.to_sql})
1241
+ AND encounter.voided = 0 #{site_manager(operator: 'AND', column: 'encounter.site_id', location: @location)}
1242
+ WHERE person_id IN (SELECT patient_id FROM temp_patient_outcomes WHERE cum_outcome = 'On antiretrovirals')
1243
+ AND concept_id IN (#{breastfeeding_concepts.to_sql})
1244
+ AND obs.voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1245
+ AND obs_datetime < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
1246
+ GROUP BY person_id
1247
+ ) AS max_obs
1248
+ ON max_obs.person_id = obs.person_id
1249
+ AND max_obs.obs_datetime = obs.obs_datetime
1250
+ WHERE obs.person_id = e.patient_id
1251
+ AND #{in_manager(column: 'obs.person_id', values: total_pregnant_women.join(','), negation: true)}
1252
+ AND obs.obs_datetime < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
1253
+ AND obs.concept_id IN (#{breastfeeding_concepts.to_sql})
1254
+ AND obs.voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1255
+ GROUP BY obs.person_id #{@adapter == 'mysql2' ? '' : ', obs.value_coded'}
1256
+ HAVING value_coded = 1065
1257
+ ORDER BY MAX(obs.obs_datetime) DESC;
1258
+ SQL
1259
+ end
1260
+
1261
+ def total_pregnant_women(_patients_list, _start_date, end_date)
1262
+ encounter_types = ::EncounterType.where('LOWER(name) IN (?)',
1263
+ ['HIV CLINIC CONSULTATION'.downcase, 'HIV STAGING'.downcase])
1264
+ .select(:encounter_type_id)
1265
+
1266
+ pregnant_concepts = ::ConceptName.where('LOWER(name) IN (?)',
1267
+ ['Is patient pregnant?'.downcase, 'patient pregnant'.downcase])
1268
+ .select(:concept_id)
1269
+
1270
+ ActiveRecord::Base.connection.select_all <<~SQL
1271
+ SELECT obs.person_id, obs.value_coded
1272
+ FROM obs obs
1273
+ INNER JOIN encounter enc
1274
+ ON enc.encounter_id = obs.encounter_id
1275
+ AND enc.voided = 0 #{site_manager(operator: 'AND', column: 'enc.site_id', location: @location)}
1276
+ AND enc.encounter_type IN (#{encounter_types.to_sql})
1277
+ INNER JOIN temp_earliest_start_date e
1278
+ ON e.patient_id = enc.patient_id #{site_manager(operator: 'AND', column: 'e.site_id', location: @location)}
1279
+ AND LEFT(e.gender, 1) = 'F'
1280
+ INNER JOIN temp_patient_outcomes
1281
+ ON temp_patient_outcomes.patient_id = e.patient_id #{site_manager(operator: 'AND', column: 'temp_patient_outcomes.site_id', location: @location)}
1282
+ AND temp_patient_outcomes.cum_outcome = 'On antiretrovirals'
1283
+ INNER JOIN (
1284
+ SELECT person_id, MAX(obs_datetime) AS obs_datetime
1285
+ FROM obs
1286
+ INNER JOIN encounter
1287
+ ON encounter.encounter_id = obs.encounter_id #{site_manager(operator: 'AND', column: 'encounter.site_id', location: @location)}
1288
+ AND encounter.encounter_type IN (#{encounter_types.to_sql})
1289
+ AND encounter.voided = 0
1290
+ WHERE concept_id IN (#{pregnant_concepts.to_sql})
1291
+ AND obs_datetime < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
1292
+ AND obs.voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1293
+ GROUP BY person_id
1294
+ ) AS max_obs
1295
+ ON max_obs.person_id = obs.person_id
1296
+ AND max_obs.obs_datetime = obs.obs_datetime
1297
+ WHERE obs.concept_id IN (#{pregnant_concepts.to_sql})
1298
+ AND obs.voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1299
+ GROUP BY obs.person_id #{@adapter == 'mysql2' ? '' : ', obs.value_coded'}
1300
+ HAVING obs.value_coded = 1065
1301
+ ORDER BY MAX(obs.obs_datetime) DESC;
1302
+ SQL
1303
+ end
1304
+
1305
+ def total_other_patients(patient_list, all_breastfeeding_women, all_pregnant_women)
1306
+ patient_ids = []
1307
+ all_pregnant_women_ids = []
1308
+ all_breastfeeding_women_ids = []
1309
+
1310
+ (patient_list || []).each do |row|
1311
+ patient_ids << row['patient_id'].to_i
1312
+ end
1313
+
1314
+ (all_pregnant_women || []).each do |row|
1315
+ all_pregnant_women_ids << row['person_id'].to_i
1316
+ end
1317
+
1318
+ (all_breastfeeding_women || []).each do |row|
1319
+ all_breastfeeding_women_ids << row['person_id'].to_i
1320
+ end
1321
+
1322
+ (patient_ids - (all_breastfeeding_women_ids + all_pregnant_women_ids))
1323
+ end
1324
+
1325
+ MIN_ART_ADHERENCE_THRESHOLD = 95.0 # Those below are not adherent
1326
+ MAX_ART_ADHERENCE_THRESHOLD = 105.0 # Thoseabove are not adherent
1327
+
1328
+ # Groups patients list into three groups based on the adherence rates
1329
+ #
1330
+ # Returns: A list of 3 lists as follows:
1331
+ # [
1332
+ # [adherent patients],
1333
+ # [inadherent patients],
1334
+ # [patients whose adherence rate is unknown]
1335
+ # ]
1336
+ def latest_art_adherence(patients_alive_and_on_art, _start_date, end_date)
1337
+ patients_alive_and_on_art = Set.new(patients_alive_and_on_art.map { |patient| patient['patient_id'] })
1338
+ end_date = ActiveRecord::Base.connection.quote(end_date)
1339
+
1340
+ not_adherent = ActiveRecord::Base.connection.select_all <<~SQL
1341
+ SELECT adherence.person_id
1342
+ FROM obs AS adherence
1343
+ INNER JOIN (
1344
+ SELECT obs.person_id, DATE(MAX(obs.obs_datetime)) AS visit_date
1345
+ FROM obs
1346
+ INNER JOIN orders
1347
+ ON orders.order_id = obs.order_id #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
1348
+ AND orders.concept_id IN (#{arv_drug_concepts.to_sql})
1349
+ AND orders.order_type_id = #{drug_order_type.order_type_id}
1350
+ AND orders.voided = 0
1351
+ INNER JOIN temp_patient_outcomes
1352
+ ON temp_patient_outcomes.patient_id = obs.person_id #{site_manager(operator: 'AND', column: 'temp_patient_outcomes.site_id', location: @location)}
1353
+ AND LOWER(temp_patient_outcomes.cum_outcome) = LOWER('On antiretrovirals')
1354
+ WHERE obs.concept_id = #{drug_order_adherence_concept.concept_id}
1355
+ AND obs.obs_datetime < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
1356
+ AND (obs.value_numeric IS NOT NULL OR obs.value_text IS NOT NULL)
1357
+ AND obs.voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1358
+ GROUP BY obs.person_id
1359
+ ) AS max_adherence
1360
+ ON max_adherence.person_id = adherence.person_id
1361
+ AND adherence.obs_datetime >= max_adherence.visit_date #{site_manager(operator: 'AND', column: 'adherence.site_id', location: @location)}
1362
+ AND adherence.obs_datetime < #{interval_manager(date: 'max_adherence.visit_date', value: 1, interval: 'DAY', operator: '+')}
1363
+ INNER JOIN orders
1364
+ ON orders.order_id = adherence.order_id
1365
+ AND orders.order_type_id = #{drug_order_type.order_type_id}
1366
+ AND orders.concept_id IN (#{arv_drug_concepts.to_sql})
1367
+ AND orders.voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
1368
+ WHERE adherence.concept_id = #{drug_order_adherence_concept.concept_id}
1369
+ AND (
1370
+ (adherence.value_numeric < #{MIN_ART_ADHERENCE_THRESHOLD} OR adherence.value_numeric > #{MAX_ART_ADHERENCE_THRESHOLD})
1371
+ OR
1372
+ (#{@adapter == 'mysql2' ? 'CAST(adherence.value_text AS SIGNED INTEGER)' : 'CAST(adherence.value_text AS INTEGER)'} < #{MIN_ART_ADHERENCE_THRESHOLD}
1373
+ OR #{@adapter == 'mysql2' ? 'CAST(adherence.value_text AS SIGNED INTEGER)' : 'CAST(adherence.value_text AS INTEGER)'} > #{MAX_ART_ADHERENCE_THRESHOLD})
1374
+ )
1375
+ AND adherence.voided = 0 #{site_manager(operator: 'AND', column: 'adherence.site_id', location: @location)}
1376
+ GROUP BY adherence.person_id
1377
+ SQL
1378
+
1379
+ not_adherent = not_adherent.empty? ? [] : not_adherent.map { |row| row['person_id'] }
1380
+
1381
+ adherent = ActiveRecord::Base.connection.select_all <<~SQL
1382
+ SELECT adherence.person_id
1383
+ FROM obs AS adherence
1384
+ INNER JOIN (
1385
+ SELECT obs.person_id, DATE(MAX(obs.obs_datetime)) AS visit_date
1386
+ FROM obs
1387
+ INNER JOIN orders
1388
+ ON orders.order_id = obs.order_id
1389
+ AND orders.concept_id IN (#{arv_drug_concepts.to_sql})
1390
+ AND orders.order_type_id = #{drug_order_type.order_type_id}
1391
+ AND orders.voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
1392
+ INNER JOIN temp_patient_outcomes
1393
+ ON temp_patient_outcomes.patient_id = obs.person_id #{site_manager(operator: 'AND', column: 'temp_patient_outcomes.site_id', location: @location)}
1394
+ AND #{in_manager(column: 'temp_patient_outcomes.patient_id', values: not_adherent.blank? ? [0] : not_adherent.join(','), negation: true)}
1395
+ AND LOWER(temp_patient_outcomes.cum_outcome) = LOWER('On antiretrovirals')
1396
+ WHERE obs.concept_id = #{drug_order_adherence_concept.concept_id}
1397
+ AND obs.obs_datetime < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
1398
+ AND (obs.value_numeric IS NOT NULL OR obs.value_text IS NOT NULL)
1399
+ AND obs.voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1400
+ GROUP BY obs.person_id
1401
+ ) AS max_adherence
1402
+ ON max_adherence.person_id = adherence.person_id
1403
+ AND adherence.obs_datetime >= max_adherence.visit_date #{site_manager(operator: 'AND', column: 'adherence.site_id', location: @location)}
1404
+ AND adherence.obs_datetime < #{interval_manager(date: 'max_adherence.visit_date', value: 1, interval: 'DAY', operator: '+')}
1405
+ INNER JOIN orders
1406
+ ON orders.order_id = adherence.order_id
1407
+ AND orders.order_type_id = #{drug_order_type.order_type_id}
1408
+ AND orders.concept_id IN (#{arv_drug_concepts.to_sql})
1409
+ AND orders.voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
1410
+ WHERE adherence.concept_id = #{drug_order_adherence_concept.concept_id}
1411
+ AND ((adherence.value_numeric >= #{MIN_ART_ADHERENCE_THRESHOLD}
1412
+ OR adherence.value_numeric <= #{MAX_ART_ADHERENCE_THRESHOLD})
1413
+ OR (#{@adapter == 'mysql2' ? 'CAST(adherence.value_text AS SIGNED INTEGER)' : 'CAST(adherence.value_text AS INTEGER)'} >= #{MIN_ART_ADHERENCE_THRESHOLD}
1414
+ OR #{@adapter == 'mysql2' ? 'CAST(adherence.value_text AS SIGNED INTEGER)' : 'CAST(adherence.value_text AS INTEGER)'} <= #{MAX_ART_ADHERENCE_THRESHOLD}))
1415
+ AND adherence.voided = 0 #{site_manager(operator: 'AND', column: 'adherence.site_id', location: @location)}
1416
+ GROUP BY adherence.person_id
1417
+ SQL
1418
+
1419
+ adherent = adherent.map { |row| row['person_id'] }
1420
+ unknown_adherence = patients_alive_and_on_art.to_a - adherent - not_adherent
1421
+ Rails.logger.info "Adherent: #{adherent.uniq.count}, Not Adherent: #{not_adherent.uniq.count}, Unknown: #{unknown_adherence.uniq.count}"
1422
+ [adherent.uniq, not_adherent.uniq, unknown_adherence]
1423
+ end
1424
+
1425
+ def adherence_encounter
1426
+ @adherence_encounter ||= encounter_type('ART ADHERENCE')
1427
+ end
1428
+
1429
+ def hiv_program
1430
+ @hiv_program ||= program('HIV PROGRAM')
1431
+ end
1432
+
1433
+ def drug_order_adherence_concept
1434
+ @drug_order_adherence_concept ||= concept('Drug order adherence')
1435
+ end
1436
+
1437
+ def drug_order_type
1438
+ @drug_order_type ||= order_type('Drug order')
1439
+ end
1440
+
1441
+ def arv_drug_concepts
1442
+ @arv_drug_concepts ||= ::ConceptSet.where(set: concept('Antiretroviral drugs'))
1443
+ .select(:concept_id)
1444
+ end
1445
+
1446
+ def write_tb_status_indicators(cohort_struct, _patients_alive_and_on_art, _start_date, end_date)
1447
+ cohort_struct.tb_suspected = []
1448
+ cohort_struct.tb_not_suspected = []
1449
+ cohort_struct.tb_confirmed_on_tb_treatment = []
1450
+ cohort_struct.tb_confirmed_currently_not_yet_on_tb_treatment = []
1451
+ cohort_struct.unknown_tb_status = []
1452
+
1453
+ tb_suspected_concept = concept('TB Suspected')
1454
+ tb_not_suspected_concept = concept('TB Not Suspected')
1455
+ tb_confirmed_but_not_on_treatment = concept('Confirmed TB NOT on Treatment')
1456
+ tb_confirmed_and_on_treatment = concept('Confirmed TB on Treatment')
1457
+
1458
+ # patients_alive_and_on_art
1459
+ (all_tb_statuses(end_date) || []).each do |data|
1460
+ tb_status_value = begin
1461
+ data['tb_status'].to_i
1462
+ rescue StandardError
1463
+ nil
1464
+ end
1465
+
1466
+ case tb_status_value
1467
+ when tb_suspected_concept.concept_id
1468
+ cohort_struct.tb_suspected << data['patient_id']
1469
+ when tb_not_suspected_concept.concept_id
1470
+ cohort_struct.tb_not_suspected << data['patient_id']
1471
+ when tb_confirmed_and_on_treatment.concept_id
1472
+ cohort_struct.tb_confirmed_on_tb_treatment << data['patient_id']
1473
+ when tb_confirmed_but_not_on_treatment.concept_id
1474
+ cohort_struct.tb_confirmed_currently_not_yet_on_tb_treatment << data['patient_id']
1475
+ else
1476
+ cohort_struct.unknown_tb_status << data['patient_id']
1477
+ end
1478
+ end
1479
+ end
1480
+
1481
+ def all_tb_statuses(end_date)
1482
+ ActiveRecord::Base.connection.select_all("
1483
+ SELECT e.*, tb_status
1484
+ FROM temp_earliest_start_date e
1485
+ LEFT JOIN temp_patient_tb_status s ON s.patient_id = e.patient_id #{site_manager(operator: 'AND',
1486
+ column: 's.site_id', location: @location)}
1487
+ INNER JOIN temp_patient_outcomes o ON o.patient_id = e.patient_id #{site_manager(operator: 'AND',
1488
+ column: 'o.site_id', location: @location)}
1489
+ WHERE LOWER(o.cum_outcome) = LOWER('On antiretrovirals') #{site_manager(operator: 'AND', column: 'e.site_id',
1490
+ location: @location)}
1491
+ AND DATE(e.date_enrolled) <= '#{end_date.to_date}';
1492
+ ")
1493
+ end
1494
+
1495
+ def get_tb_status(tb_status)
1496
+ registered = []
1497
+ (@tb_status || []).each do |status|
1498
+ if tb_status == status[:tb_status]
1499
+ registered << { patient_id: status[:patient_id], tb_status: status[:tb_status] }
1500
+ end
1501
+ end
1502
+
1503
+ registered
1504
+ end
1505
+
1506
+ def patients_side_effects_status(_patients_alive_and_on_art, end_date)
1507
+ with_side_effects = []
1508
+ without_side_effects = []
1509
+ unknowns = []
1510
+
1511
+ records = ActiveRecord::Base.connection.select_all <<~SQL
1512
+ SELECT e.*, s.has_se
1513
+ FROM temp_earliest_start_date e
1514
+ INNER JOIN temp_patient_side_effects s ON s.patient_id = e.patient_id #{site_manager(operator: 'AND', column: 's.site_id', location: @location)}
1515
+ INNER JOIN temp_patient_outcomes o ON o.patient_id = e.patient_id #{site_manager(operator: 'AND', column: 'o.site_id', location: @location)}
1516
+ WHERE LOWER(o.cum_outcome) = LOWER('On antiretrovirals') #{site_manager(operator: 'AND', column: 'e.site_id', location: @location)}
1517
+ AND DATE(e.date_enrolled) <= '#{end_date.to_date}';
1518
+ SQL
1519
+
1520
+ (records || []).each do |data|
1521
+ if data['has_se'] == 'Yes'
1522
+ with_side_effects << data['patient_id']
1523
+ elsif data['has_se'] == 'No'
1524
+ without_side_effects << data['patient_id']
1525
+ else
1526
+ unknowns << data['patient_id']
1527
+ end
1528
+ end
1529
+
1530
+ [with_side_effects, without_side_effects, unknowns]
1531
+ end
1532
+
1533
+ COHORT_REGIMENS = %w[
1534
+ 0P 2P 4PP 4PA 9PP 9PA 11PP 11PA 12PP 12PA 14PP 14PA 15PP 15PA 16P 17PP 17PA
1535
+ 4A 5A 6A 7A 8A 9A 10A 11A 12A 13A 14A 15A 16A 17A
1536
+ ].freeze
1537
+
1538
+ def cal_regimem_category(_patient_list, end_date)
1539
+ MalawiHivProgramReports::Cohort::Regimens.new.patient_regimens(end_date, @location).map do |prescription|
1540
+ regimen = prescription['regimen_category']&.upcase
1541
+
1542
+ regimen = 'unknown_regimen' if regimen == 'Unknown'.upcase || !COHORT_REGIMENS.include?(regimen)
1543
+
1544
+ {
1545
+ patient_id: prescription['patient_id'],
1546
+ regimen_category: regimen,
1547
+ drugs: prescription['drugs'].split(',').collect(&:to_i),
1548
+ prescription_date: prescription['prescription_date']
1549
+ }
1550
+ end
1551
+ end
1552
+
1553
+ def filter_prescriptions_by_regimen(prescriptions, regimen)
1554
+ prescriptions.select do |prescription|
1555
+ prescription[:regimen_category].casecmp?(regimen)
1556
+ end
1557
+ end
1558
+
1559
+ def filter_prescriptions_by_drugs(prescriptions, drug_ids)
1560
+ prescriptions.select do |prescription|
1561
+ prescription[:drugs].find { |drug_id| drug_ids.include?(drug_id) }
1562
+ end
1563
+ end
1564
+
1565
+ def died_in(month_str)
1566
+ registered = []
1567
+ if month_str == '4+ months'
1568
+ data = ActiveRecord::Base.connection.select_all(
1569
+ "SELECT t.patient_id, #{function_manager(function: 'died_in', location: @location,
1570
+ args: "t.patient_id, cum_outcome, earliest_start_date, #{@location}")} died_in
1571
+ FROM temp_patient_outcomes o
1572
+ INNER JOIN temp_earliest_start_date t ON t.patient_id = o.patient_id #{site_manager(operator: 'AND',
1573
+ column: 't.site_id', location: @location)}
1574
+ WHERE cum_outcome = 'Patient died' #{site_manager(operator: 'AND', column: 'o.site_id',
1575
+ location: @location)}
1576
+ GROUP BY t.patient_id #{@adapter == 'mysql2' ? '' : ', cum_outcome, earliest_start_date'}
1577
+ HAVING LOWER(#{if @adapter == 'mysql2'
1578
+ 'died_in'
1579
+ else
1580
+ function_manager(function: 'died_in', location: @location,
1581
+ args: "t.patient_id, cum_outcome, earliest_start_date, #{@location}")
1582
+ end}) IN (LOWER('4+ months'), LOWER('Unknown'))"
1583
+ )
1584
+ else
1585
+ data = ActiveRecord::Base.connection.select_all(
1586
+ "SELECT t.patient_id, #{function_manager(function: 'died_in', location: @location,
1587
+ args: "t.patient_id, cum_outcome, earliest_start_date, #{@location}")} died_in
1588
+ FROM temp_patient_outcomes o
1589
+ INNER JOIN temp_earliest_start_date t ON t.patient_id = o.patient_id #{site_manager(operator: 'AND',
1590
+ column: 't.site_id', location: @location)}
1591
+ WHERE cum_outcome = 'Patient died' #{site_manager(operator: 'AND', column: 'o.site_id',
1592
+ location: @location)}
1593
+ GROUP BY t.patient_id #{@adapter == 'mysql2' ? '' : ', cum_outcome, earliest_start_date'}
1594
+ HAVING LOWER(#{if @adapter == 'mysql2'
1595
+ 'died_in'
1596
+ else
1597
+ function_manager(function: 'died_in', location: @location,
1598
+ args: "t.patient_id, cum_outcome, earliest_start_date, #{@location}")
1599
+ end}) = '#{month_str.downcase}'"
1600
+ )
1601
+ end
1602
+
1603
+ (data || []).each do |patient|
1604
+ registered << patient['patient_id']
1605
+ end
1606
+
1607
+ registered
1608
+ end
1609
+
1610
+ def get_outcome(outcome)
1611
+ sql_patch = if outcome.casecmp('Pre-ART (Continue)').zero?
1612
+ "LOWER(cum_outcome) = '#{outcome.downcase}' OR LOWER(cum_outcome) = LOWER('Unknown')"
1613
+ else
1614
+ "LOWER(cum_outcome) = '#{outcome.downcase}'"
1615
+ end
1616
+
1617
+ ActiveRecord::Base.connection.select_all(
1618
+ "SELECT * FROM temp_patient_outcomes WHERE #{sql_patch} #{site_manager(operator: 'AND', column: 'site_id',
1619
+ location: @location)} GROUP BY patient_id"
1620
+ )
1621
+ end
1622
+
1623
+ def kaposis_sarcoma(start_date, end_date)
1624
+ # KAPOSIS SARCOMA
1625
+ concept_id = concept_name('KAPOSIS SARCOMA').concept_id
1626
+ yes_concept_id = concept_name('Yes').concept_id
1627
+ who_stages_criteria = concept_name('Who stages criteria present').concept_id
1628
+
1629
+ ActiveRecord::Base.connection.select_all <<~SQL
1630
+ SELECT t.patient_id
1631
+ FROM temp_earliest_start_date t
1632
+ INNER JOIN obs ON t.patient_id = obs.person_id #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)} AND obs.voided = 0
1633
+ WHERE t.date_enrolled BETWEEN '#{start_date}' AND '#{end_date}'
1634
+ 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}))
1635
+ AND DATE(obs_datetime) <= DATE(date_enrolled) #{site_manager(operator: 'AND', column: 't.site_id', location: @location)}
1636
+ GROUP BY patient_id
1637
+ SQL
1638
+ end
1639
+
1640
+ def current_episode_of_tb(start_date, end_date)
1641
+ # CURRENT EPISODE OF TB
1642
+ eptb_concept_id = concept('EXTRAPULMONARY TUBERCULOSIS (EPTB)').concept_id
1643
+ yes_concept_id = concept('Yes').concept_id
1644
+ pulmonary_tb_concept_id = concept('PULMONARY TUBERCULOSIS').concept_id
1645
+ current_ptb_concept_id = concept('PULMONARY TUBERCULOSIS (CURRENT)').concept_id
1646
+ who_stages_criteria = concept('Who stages criteria present').concept_id
1647
+
1648
+ ActiveRecord::Base.connection.select_all <<~SQL
1649
+ SELECT t.patient_id
1650
+ FROM temp_earliest_start_date t
1651
+ INNER JOIN obs ON t.patient_id = obs.person_id #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1652
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}'
1653
+ AND ( (#{in_manager(column: 'value_coded', values: [eptb_concept_id, pulmonary_tb_concept_id, current_ptb_concept_id])} AND concept_id = #{who_stages_criteria} )
1654
+ OR#{' '}
1655
+ (#{in_manager(column: 'concept_id', values: [eptb_concept_id, pulmonary_tb_concept_id, current_ptb_concept_id])} AND value_coded = #{yes_concept_id}))
1656
+ AND voided = 0 AND DATE(obs_datetime) <= DATE(date_enrolled) #{site_manager(operator: 'AND', column: 't.site_id', location: @location)}
1657
+ GROUP BY patient_id
1658
+ SQL
1659
+ end
1660
+
1661
+ def tb_within_the_last_two_years(patients_with_current_tb, start_date, end_date)
1662
+ # patients with current episode of tb
1663
+ patients_with_current_tb_episode = []
1664
+ (patients_with_current_tb || []).each do |patient|
1665
+ patients_with_current_tb_episode << patient['patient_id'].to_i
1666
+ end
1667
+
1668
+ patients_with_current_tb_episode = [0] if patients_with_current_tb_episode.blank?
1669
+
1670
+ # Pulmonary tuberculosis within the last 2 years
1671
+ pulmonary_tb_within_last_2yrs_concept_id = concept('Pulmonary tuberculosis within the last 2 years').concept_id
1672
+ ptb_within_the_past_two_yrs_concept_id = concept('Ptb within the past two years').concept_id
1673
+ who_stages_criteria = concept('Who stages criteria present').concept_id
1674
+ yes_concept_id = concept('Yes').concept_id
1675
+
1676
+ ActiveRecord::Base.connection.select_all(
1677
+ "SELECT t.patient_id
1678
+ FROM temp_earliest_start_date t
1679
+ INNER JOIN obs ON t.patient_id = obs.person_id #{site_manager(operator: 'AND', column: 'obs.site_id',
1680
+ location: @location)}
1681
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}'
1682
+ AND ((#{in_manager(column: 'value_coded',
1683
+ values: [pulmonary_tb_within_last_2yrs_concept_id,
1684
+ ptb_within_the_past_two_yrs_concept_id])}
1685
+ AND concept_id = #{who_stages_criteria})
1686
+ OR ( #{in_manager(column: 'concept_id',
1687
+ values: [pulmonary_tb_within_last_2yrs_concept_id,
1688
+ ptb_within_the_past_two_yrs_concept_id])} AND value_coded = #{yes_concept_id}))
1689
+ AND #{in_manager(column: 'patient_id', values: patients_with_current_tb_episode.join(','),
1690
+ negation: true)}
1691
+ AND voided = 0 AND DATE(obs_datetime) <= DATE(date_enrolled) #{site_manager(operator: 'AND',
1692
+ column: 't.site_id', location: @location)}
1693
+ GROUP BY patient_id"
1694
+ )
1695
+ end
1696
+
1697
+ def no_tb(total_registered, tb_within_the_last_two_years, current_episode_of_tb)
1698
+ total_registered_patients = []
1699
+ tb_within_2yrs_patients = []
1700
+ current_tb_episode_patients = []
1701
+
1702
+ (total_registered || []).each do |patient|
1703
+ total_registered_patients << patient['patient_id'].to_i
1704
+ end
1705
+
1706
+ (tb_within_the_last_two_years || []).each do |patient|
1707
+ tb_within_2yrs_patients << patient['patient_id'].to_i
1708
+ end
1709
+
1710
+ (current_episode_of_tb || []).each do |patient|
1711
+ current_tb_episode_patients << patient['patient_id'].to_i
1712
+ end
1713
+
1714
+ total_registered_patients - (tb_within_2yrs_patients + current_tb_episode_patients)
1715
+ end
1716
+
1717
+ def cum_no_tb(cum_total_registered, cum_tb_within_the_last_two_years, cum_current_episode_of_tb)
1718
+ total_registered_patients = []
1719
+ tb_within_2yrs_patients = []
1720
+ current_tb_episode_patients = []
1721
+
1722
+ (cum_total_registered || []).each do |patient|
1723
+ total_registered_patients << patient['patient_id'].to_i
1724
+ end
1725
+
1726
+ (cum_tb_within_the_last_two_years || []).each do |patient|
1727
+ tb_within_2yrs_patients << patient['patient_id'].to_i
1728
+ end
1729
+
1730
+ (cum_current_episode_of_tb || []).each do |patient|
1731
+ current_tb_episode_patients << patient['patient_id'].to_i
1732
+ end
1733
+
1734
+ total_registered_patients - (tb_within_2yrs_patients + current_tb_episode_patients)
1735
+ end
1736
+
1737
+ def children_12_59_months(start_date, end_date)
1738
+ concept = ::ConceptName.where('LOWER(name) IN (?)', 'HIV Infected'.downcase).select(:concept_id)
1739
+
1740
+ find_patients_by_reason_for_starting(start_date, end_date, concept)
1741
+ end
1742
+
1743
+ def unknown_other_reason_outside_guidelines(start_date, end_date)
1744
+ # All WHO stage 1 and 2 patients that were enrolled before '2016-04-01'
1745
+ # should be included in this group.
1746
+ unknown_concepts = ::ConceptName.where('LOWER(name) IN (?)', ['Unknown'.downcase, 'None'.downcase])
1747
+ .select(:concept_id)
1748
+ .to_sql
1749
+
1750
+ if start_date.to_date > '2016-04-01'.to_date
1751
+ return ActiveRecord::Base.connection.select_all <<~SQL
1752
+ SELECT patient_id
1753
+ FROM temp_earliest_start_date
1754
+ WHERE reason_for_starting_art IN (#{unknown_concepts})
1755
+ AND date_enrolled >= '#{start_date}'
1756
+ AND date_enrolled <= '#{end_date}' #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
1757
+ SQL
1758
+ end
1759
+
1760
+ stage_1_and_2_concept_names = ['LYMPHOCYTE COUNT BELOW THRESHOLD WITH WHO STAGE 1'.downcase,
1761
+ 'LYMPHOCYTES'.downcase,
1762
+ 'LYMPHOCYTE COUNT BELOW THRESHOLD WITH WHO STAGE 2'.downcase,
1763
+ 'WHO stage I adult'.downcase,
1764
+ 'WHO stage I peds'.downcase,
1765
+ 'WHO STAGE 1'.downcase,
1766
+ 'WHO stage II adult'.downcase,
1767
+ 'WHO stage II peds'.downcase,
1768
+ 'WHO STAGE 2'.downcase]
1769
+
1770
+ stage_1_and_2_concepts = ::ConceptName.where('LOWER(name) IN (?)', stage_1_and_2_concept_names)
1771
+ .select('DISTINCT concept_id')
1772
+ .to_sql
1773
+
1774
+ ActiveRecord::Base.connection.select_all <<~SQL
1775
+ SELECT patient_id
1776
+ FROM temp_earliest_start_date
1777
+ WHERE
1778
+ (
1779
+ reason_for_starting_art IN (#{unknown_concepts})
1780
+ AND date_enrolled >= '#{start_date}'
1781
+ AND date_enrolled <= '#{end_date}'
1782
+ )
1783
+ OR (
1784
+ reason_for_starting_art IN (#{stage_1_and_2_concepts})
1785
+ AND date_enrolled <= DATE('2016-04-01')
1786
+ ) #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
1787
+ SQL
1788
+ end
1789
+
1790
+ def who_stage_four(start_date, end_date)
1791
+ concepts = ::ConceptName.where('LOWER(name) IN (?)',
1792
+ ['WHO stage IV adult'.downcase, 'WHO stage IV peds'.downcase,
1793
+ 'WHO STAGE 4'.downcase])
1794
+ .select(:concept_id)
1795
+ find_patients_by_reason_for_starting(start_date, end_date, concepts)
1796
+ end
1797
+
1798
+ def who_stage_three(start_date, end_date)
1799
+ concepts = ::ConceptName.where('LOWER(name) IN (?)',
1800
+ ['WHO stage III adult'.downcase, 'WHO stage III peds'.downcase,
1801
+ 'WHO STAGE 3'.downcase])
1802
+ .select(:concept_id)
1803
+ find_patients_by_reason_for_starting(start_date, end_date, concepts)
1804
+ end
1805
+
1806
+ def pregnant_women(start_date, end_date)
1807
+ concepts = ::ConceptName.where('LOWER(name) IN (?)', ['PATIENT PREGNANT'.downcase, 'Is patient pregnant at initiation?'.downcase,
1808
+ 'Patient pregnant state'.downcase, 'Is patient pregnant?'.downcase])
1809
+ .select(:concept_id)
1810
+ find_patients_by_reason_for_starting(start_date, end_date, concepts)
1811
+ end
1812
+
1813
+ def breastfeeding_mothers(start_date, end_date)
1814
+ concept = ::ConceptName.where('LOWER(name) IN (?)', 'Breastfeeding'.downcase).select(:concept_id)
1815
+ find_patients_by_reason_for_starting(start_date, end_date, concept)
1816
+ end
1817
+
1818
+ def asymptomatic(start_date, end_date)
1819
+ # for WHO stage 1 and 2 to be included in asymptomatic, the patients are supposed to
1820
+ # be enrolled on HIV PROGRAM after 2016-04-01
1821
+ patients = []
1822
+
1823
+ asymptomatic_concepts = ::ConceptName.where('LOWER(name) IN (?)',
1824
+ ['ASYMPTOMATIC'.downcase, 'Asymptomatic HIV infection'.downcase])
1825
+ .select(:concept_id)
1826
+ find_patients_by_reason_for_starting(start_date, end_date, asymptomatic_concepts)
1827
+ .each { |patient| patients << patient['patient_id'] }
1828
+
1829
+ reason_concepts = ::ConceptName.where(name: ['WHO stage I adult'.downcase,
1830
+ 'WHO stage I peds'.downcase,
1831
+ 'WHO stage 1'.downcase,
1832
+ 'WHO stage II adult'.downcase,
1833
+ 'WHO stage II peds'.downcase,
1834
+ 'LYMPHOCYTE COUNT BELOW THRESHOLD WITH WHO STAGE 1'.downcase,
1835
+ 'LYMPHOCYTES'.downcase,
1836
+ 'LYMPHOCYTE COUNT BELOW THRESHOLD WITH WHO STAGE 2'.downcase])
1837
+ .select(:concept_id)
1838
+
1839
+ revised_art_guidelines_date = '2016-04-01'.to_date
1840
+ start_date = revised_art_guidelines_date if start_date.to_date < revised_art_guidelines_date
1841
+
1842
+ find_patients_by_reason_for_starting(start_date, end_date, reason_concepts)
1843
+ .each { |patient| patients << patient['patient_id'] }
1844
+
1845
+ patients
1846
+ end
1847
+
1848
+ def who_stage_two(start_date, end_date)
1849
+ concepts = ::ConceptName.where('LOWER(name) IN (?)', ['CD4 COUNT LESS THAN OR EQUAL TO 750'.downcase,
1850
+ 'CD4 count less than or equal to 500'.downcase,
1851
+ 'CD4 COUNT LESS THAN OR EQUAL TO 350'.downcase,
1852
+ 'CD4 count less than 350'.downcase,
1853
+ 'CD4 count less than 250'.downcase,
1854
+ 'CD4 COUNT LESS THAN OR EQUAL TO 250'.downcase])
1855
+ .select(:concept_id)
1856
+ find_patients_by_reason_for_starting(start_date, end_date, concepts)
1857
+ end
1858
+
1859
+ def confirmed_hiv_infection_in_infants_pcr(start_date, end_date)
1860
+ concept = ::ConceptName.where('LOWER(name) IN (?)', 'HIV PCR'.downcase).select(:concept_id)
1861
+ find_patients_by_reason_for_starting(start_date, end_date, concept)
1862
+ end
1863
+
1864
+ def presumed_severe_hiv_disease_in_infants(start_date, end_date)
1865
+ concepts = ::ConceptName.where('LOWER(name) IN (?)', ['PRESUMED SEVERE HIV'.downcase,
1866
+ 'PRESUMED SEVERE HIV CRITERIA IN INFANTS'.downcase])
1867
+ .select(:concept_id)
1868
+ find_patients_by_reason_for_starting(start_date, end_date, concepts)
1869
+ end
1870
+
1871
+ def find_patients_by_reason_for_starting(start_date, end_date, reason_concept_ids)
1872
+ ActiveRecord::Base.connection.select_all <<~SQL
1873
+ SELECT patient_id
1874
+ FROM temp_earliest_start_date
1875
+ WHERE date_enrolled >= '#{start_date}'
1876
+ AND date_enrolled <= '#{end_date}'
1877
+ AND reason_for_starting_art IN (#{reason_concept_ids.to_sql}) #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
1878
+ SQL
1879
+ end
1880
+
1881
+ def unknown_age(start_date, end_date)
1882
+ ActiveRecord::Base.connection.select_all(
1883
+ "SELECT patient_id
1884
+ FROM temp_earliest_start_date
1885
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}' #{site_manager(operator: 'AND',
1886
+ column: 'site_id', location: @location)}
1887
+ AND (age_at_initiation IS NULL OR age_at_initiation < 0 OR birthdate IS NULL)
1888
+ GROUP BY patient_id"
1889
+ )
1890
+ end
1891
+
1892
+ def unknown_gender(start_date, end_date)
1893
+ ActiveRecord::Base.connection.select_all(
1894
+ "SELECT patient_id
1895
+ FROM temp_earliest_start_date
1896
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}' #{site_manager(operator: 'AND',
1897
+ column: 'site_id', location: @location)}
1898
+ AND gender IS NULL OR LENGTH(gender) < 1 GROUP BY patient_id;"
1899
+ )
1900
+ end
1901
+
1902
+ def adults_at_art_initiation(start_date, end_date)
1903
+ ActiveRecord::Base.connection.select_all(
1904
+ "SELECT patient_id FROM temp_earliest_start_date
1905
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}' #{site_manager(operator: 'AND',
1906
+ column: 'site_id', location: @location)}
1907
+ AND age_at_initiation > 14 GROUP BY patient_id"
1908
+ )
1909
+ end
1910
+
1911
+ def children_24_months_14_years_at_art_initiation(start_date, end_date)
1912
+ ActiveRecord::Base.connection.select_all(
1913
+ "SELECT patient_id FROM temp_earliest_start_date
1914
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}' #{site_manager(operator: 'AND',
1915
+ column: 'site_id', location: @location)}
1916
+ AND age_at_initiation BETWEEN 2 AND 14 GROUP BY patient_id"
1917
+ )
1918
+ end
1919
+
1920
+ def children_below_24_months_at_art_initiation(start_date, end_date)
1921
+ ActiveRecord::Base.connection.select_all(
1922
+ "SELECT patient_id FROM temp_earliest_start_date
1923
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}' #{site_manager(operator: 'AND',
1924
+ column: 'site_id', location: @location)}
1925
+ AND (age_at_initiation >= 0 AND age_at_initiation < 2) GROUP BY patient_id"
1926
+ )
1927
+ end
1928
+
1929
+ def non_pregnant_females(start_date, end_date, pregnant_women = [])
1930
+ pregnant_women_ids = []
1931
+ (pregnant_women || []).each do |patient|
1932
+ pregnant_women_ids << patient
1933
+ end
1934
+ pregnant_women_ids = [0] if pregnant_women_ids.blank?
1935
+
1936
+ ActiveRecord::Base.connection.select_all <<~SQL
1937
+ SELECT t.patient_id
1938
+ FROM temp_earliest_start_date t
1939
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}' #{site_manager(operator: 'AND', column: 't.site_id', location: @location)}
1940
+ AND (t.gender = 'F' OR t.gender = 'Female')
1941
+ AND t.patient_id NOT IN(#{pregnant_women_ids.join(',')})
1942
+ GROUP BY t.patient_id
1943
+ SQL
1944
+ end
1945
+
1946
+ def pregnant_females_all_ages(start_date, end_date)
1947
+ yes_concept_id = concept('Yes').concept_id
1948
+ preg_concept_id = concept('IS PATIENT PREGNANT?').concept_id
1949
+ patient_preg_concept_id = concept('PATIENT PREGNANT').concept_id
1950
+ preg_at_initiation_concept_id = concept('PREGNANT AT INITIATION?').concept_id
1951
+ reason_for_starting_concept_id = concept('Reason for ART eligibility').concept_id
1952
+
1953
+ # (patient_id_plus_date_enrolled || []).each do |patient_id, date_enrolled|
1954
+ registered = ActiveRecord::Base.connection.select_all <<~SQL
1955
+ SELECT patients.*, obs.value_coded
1956
+ FROM temp_earliest_start_date AS patients
1957
+ INNER JOIN obs
1958
+ ON obs.person_id = patients.patient_id
1959
+ 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])}
1960
+ AND obs.obs_datetime >= patients.earliest_start_date
1961
+ AND obs.obs_datetime < #{interval_manager(date: 'patients.earliest_start_date', value: 1, interval: 'DAY', operator: '+')}
1962
+ AND #{in_manager(column: 'obs.value_coded', values: [yes_concept_id, patient_preg_concept_id])}
1963
+ AND obs.voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
1964
+ WHERE patients.gender IN ('F','Female')
1965
+ AND patients.date_enrolled BETWEEN '#{start_date}' AND '#{end_date}' #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
1966
+ GROUP BY patient_id #{@adapter == 'mysql2' ? '' : ',obs.value_coded'}
1967
+ SQL
1968
+
1969
+ pregnant_at_initiation = ActiveRecord::Base.connection.select_all <<~SQL
1970
+ SELECT patient_id, #{function_manager(function: 'patient_reason_for_starting_art', location: @location,
1971
+ args: "patient_id, #{@location}")} reason_concept_id
1972
+ FROM temp_earliest_start_date
1973
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}'
1974
+ AND gender IN ('F','Female') #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
1975
+ GROUP BY patient_id
1976
+ HAVING #{if @adapter == 'mysql2'
1977
+ 'reason_concept_id IN (1755, 7972, 6131)'
1978
+ else
1979
+ in_manager(column: function_manager(function: 'patient_reason_for_starting_art', location: @location, args: "patient_id, #{@location}").to_s,
1980
+ values: [
1981
+ 1755, 7972, 6131
1982
+ ])
1983
+ end};
1984
+ SQL
1985
+ pregnant_at_initiation_ids = []
1986
+ (pregnant_at_initiation || []).each do |patient|
1987
+ pregnant_at_initiation_ids << patient['patient_id'].to_i
1988
+ end
1989
+
1990
+ pregnant_at_initiation_ids = [0] if pregnant_at_initiation_ids.blank?
1991
+
1992
+ transfer_ins_women = ActiveRecord::Base.connection.select_all <<~SQL
1993
+ SELECT patient_id, #{function_manager(function: 're_initiated_check', location: @location,
1994
+ args: "patient_id, date_enrolled, #{@location}")} re_initiated
1995
+ FROM temp_earliest_start_date
1996
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}'
1997
+ AND DATE(date_enrolled) != DATE(earliest_start_date)
1998
+ AND gender IN ('F','Female') #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
1999
+ AND #{in_manager(column: 'patient_id', values: pregnant_at_initiation_ids)}
2000
+ GROUP BY patient_id
2001
+ HAVING #{ if @adapter == 'mysql2'
2002
+ 're_initiated'
2003
+ else
2004
+ function_manager(function: 're_initiated_check', location: @location,
2005
+ args: "patient_id, date_enrolled, #{@location}")
2006
+ end} != 'Re-initiated'
2007
+ SQL
2008
+
2009
+ transfer_ins_preg_women = []
2010
+ all_pregnant_females = []
2011
+ (transfer_ins_women || []).each do |patient|
2012
+ transfer_ins_preg_women << patient['patient_id'].to_i if patient['patient_id'].to_i != 0
2013
+ end
2014
+
2015
+ (registered || []).each do |patient|
2016
+ all_pregnant_females << patient['patient_id'].to_i if patient['patient_id'].to_i != 0
2017
+ end
2018
+
2019
+ (all_pregnant_females + transfer_ins_preg_women).uniq
2020
+ end
2021
+
2022
+ def initial_females_all_ages(start_date, end_date, data)
2023
+ clients = []
2024
+ women = ActiveRecord::Base.connection.select_all("
2025
+ SELECT * FROM temp_earliest_start_date e
2026
+ WHERE patient_id IN(#{data.length.positive? ? data.join(',') : 0}) #{site_manager(operator: 'AND',
2027
+ column: 'e.site_id', location: @location)}
2028
+ AND date_enrolled BETWEEN '#{start_date.to_date}' AND '#{end_date.to_date}'
2029
+ AND DATE(date_enrolled) = DATE(earliest_start_date);")
2030
+
2031
+ (women || []).each do |w|
2032
+ clients << w
2033
+ end
2034
+
2035
+ clients
2036
+ end
2037
+
2038
+ def males(start_date, end_date)
2039
+ ActiveRecord::Base.connection.select_all(
2040
+ "SELECT * FROM temp_earliest_start_date t
2041
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}' #{site_manager(operator: 'AND',
2042
+ column: 'site_id', location: @location)}
2043
+ AND (gender = 'Male' OR gender = 'M') GROUP BY patient_id"
2044
+ )
2045
+ end
2046
+
2047
+ def transfer_in(start_date, end_date, re_initiated_on_art)
2048
+ start_date = ActiveRecord::Base.connection.quote(start_date)
2049
+ end_date = ActiveRecord::Base.connection.quote(end_date)
2050
+
2051
+ re_initiated_on_art = re_initiated_on_art.empty? ? [0] : re_initiated_on_art.rows.collect(&:first)
2052
+
2053
+ ActiveRecord::Base.connection.select_all <<~SQL
2054
+ SELECT temp_earliest_start_date.patient_id
2055
+ FROM temp_earliest_start_date
2056
+ INNER JOIN clinic_registration_encounter
2057
+ ON clinic_registration_encounter.patient_id = temp_earliest_start_date.patient_id #{site_manager(operator: 'AND', column: 'clinic_registration_encounter.site_id', location: @location)}
2058
+ LEFT JOIN ever_registered_obs
2059
+ ON ever_registered_obs.person_id = temp_earliest_start_date.patient_id #{site_manager(operator: 'AND', column: 'ever_registered_obs.site_id', location: @location)}
2060
+ AND ever_registered_obs.value_coded = (
2061
+ SELECT concept_id FROM concept_name WHERE LOWER(name) = LOWER('Yes') AND voided = 0 LIMIT 1
2062
+ )
2063
+ LEFT JOIN (
2064
+ SELECT person_id, MIN(obs_datetime) AS obs_datetime
2065
+ FROM ever_registered_obs #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)}
2066
+ GROUP BY person_id
2067
+ ) AS max_ever_registered_obs
2068
+ ON max_ever_registered_obs.person_id = ever_registered_obs.person_id
2069
+ AND max_ever_registered_obs.obs_datetime = ever_registered_obs.obs_datetime
2070
+ LEFT JOIN obs AS last_taken_art_obs
2071
+ ON last_taken_art_obs.encounter_id = ever_registered_obs.encounter_id
2072
+ AND last_taken_art_obs.voided = 0 #{site_manager(operator: 'AND', column: 'last_taken_art_obs.site_id', location: @location)}
2073
+ AND last_taken_art_obs.concept_id = (
2074
+ SELECT concept_id FROM concept_name WHERE LOWER(name) = LOWER('DATE ART LAST TAKEN') LIMIT 1
2075
+ )
2076
+ WHERE (date_enrolled BETWEEN #{start_date} AND #{end_date})
2077
+ AND date_enrolled != earliest_start_date #{site_manager(operator: 'AND', column: 'temp_earliest_start_date.site_id', location: @location)}
2078
+ AND COALESCE(#{
2079
+ if @adapter == 'mysql2'
2080
+ <<~SQL
2081
+ TIMESTAMPDIFF(day,last_taken_art_obs.value_datetime,last_taken_art_obs.obs_datetime) <= 14
2082
+ SQL
2083
+ else
2084
+ <<~SQL
2085
+ EXTRACT(DAY FROM AGE(last_taken_art_obs.obs_datetime::DATE, last_taken_art_obs.value_datetime::DATE)) <= 14
2086
+ SQL
2087
+ end
2088
+ }, TRUE)
2089
+ AND #{in_manager(column: 'temp_earliest_start_date.patient_id', values: re_initiated_on_art, negation: true)}
2090
+ GROUP BY temp_earliest_start_date.patient_id;
2091
+ SQL
2092
+ end
2093
+
2094
+ def re_initiated_on_art(start_date, end_date)
2095
+ ActiveRecord::Base.connection.select_all <<~SQL
2096
+ SELECT temp_earliest_start_date.patient_id
2097
+ FROM temp_earliest_start_date
2098
+ INNER JOIN clinic_registration_encounter
2099
+ ON temp_earliest_start_date.patient_id = clinic_registration_encounter.patient_id #{site_manager(operator: 'AND', column: 'clinic_registration_encounter.site_id', location: @location)}
2100
+ INNER JOIN ever_registered_obs
2101
+ ON clinic_registration_encounter.encounter_id = ever_registered_obs.encounter_id
2102
+ AND ever_registered_obs.value_coded = (SELECT concept_id FROM concept_name
2103
+ WHERE LOWER(name) = LOWER('Yes') AND voided = 0 LIMIT 1) #{site_manager(operator: 'AND', column: 'ever_registered_obs.site_id', location: @location)}
2104
+ INNER JOIN (
2105
+ SELECT person_id, MIN(obs_datetime) AS obs_datetime
2106
+ FROM ever_registered_obs #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)}
2107
+ GROUP BY person_id
2108
+ ) AS max_ever_registered_obs
2109
+ ON max_ever_registered_obs.person_id = ever_registered_obs.person_id
2110
+ AND max_ever_registered_obs.obs_datetime = ever_registered_obs.obs_datetime
2111
+ INNER JOIN obs AS last_taken_art_obs
2112
+ ON last_taken_art_obs.encounter_id = clinic_registration_encounter.encounter_id
2113
+ AND last_taken_art_obs.voided = 0 #{site_manager(operator: 'AND', column: 'last_taken_art_obs.site_id', location: @location)}
2114
+ AND last_taken_art_obs.concept_id = (
2115
+ SELECT concept_id FROM concept_name WHERE LOWER(name) = LOWER('DATE ART LAST TAKEN') LIMIT 1
2116
+ )
2117
+ WHERE (date_enrolled BETWEEN '#{start_date}' AND '#{end_date}')
2118
+ AND #{timestampdiff_manager(date1: 'last_taken_art_obs.value_datetime', date2: 'last_taken_art_obs.obs_datetime', interval: 'day')} > 14
2119
+ AND date_enrolled != earliest_start_date #{site_manager(operator: 'AND', column: 'temp_earliest_start_date.site_id', location: @location)}
2120
+ GROUP BY temp_earliest_start_date.patient_id;
2121
+ SQL
2122
+ end
2123
+
2124
+ def initiated_on_art_first_time(start_date, end_date)
2125
+ ActiveRecord::Base.connection.select_all(
2126
+ "SELECT * FROM temp_earliest_start_date
2127
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}'
2128
+ AND date_enrolled = earliest_start_date #{site_manager(operator: 'AND', column: 'site_id',
2129
+ location: @location)}
2130
+ GROUP BY patient_id"
2131
+ )
2132
+ end
2133
+
2134
+ def males_initiated_on_art_first_time(start_date, end_date, data)
2135
+ clients = []
2136
+ (data || []).each do |e|
2137
+ gender = e['gender']&.upcase&.first
2138
+ next if gender.blank?
2139
+ next unless gender == 'M'
2140
+
2141
+ date_enrolled = e['date_enrolled'].to_date
2142
+ start_date = start_date.to_date
2143
+ end_date = end_date.to_date
2144
+ date_enrolled >= start_date && date_enrolled <= end_date ? clients << e : next
2145
+ end
2146
+
2147
+ clients
2148
+ end
2149
+
2150
+ def get_cum_start_date
2151
+ cum_start_date = ActiveRecord::Base.connection.select_value <<~SQL
2152
+ SELECT MIN(date_enrolled) FROM temp_earliest_start_date #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)}
2153
+ SQL
2154
+
2155
+ begin
2156
+ cum_start_date.to_date
2157
+ rescue StandardError
2158
+ nil
2159
+ end
2160
+ end
2161
+
2162
+ def total_registered(start_date, end_date)
2163
+ ActiveRecord::Base.connection.select_all(
2164
+ "SELECT * FROM temp_earliest_start_date
2165
+ WHERE date_enrolled BETWEEN '#{start_date}' AND '#{end_date}' #{site_manager(operator: 'AND',
2166
+ column: 'site_id', location: @location)}
2167
+ GROUP BY patient_id"
2168
+ )
2169
+ end
2170
+
2171
+ def load_tmp_patient_table(_cohort_struct)
2172
+ create_tmp_patient_table
2173
+ # arv_orders.each_with_object({}) do |order, patient_tab|
2174
+ # next if patient_tab.include?(order.patient_id)
2175
+ #
2176
+ # person = ::Person.find(order.patient_id)
2177
+ # next unless person.birthdate # && patient_in_program?(person.patient)
2178
+ #
2179
+ # add_patient_record(person, order, cohort_struct)
2180
+ #
2181
+ # patient_tab[order.patient_id] = person
2182
+ # end
2183
+ end
2184
+
2185
+ def create_tmp_patient_table_2(_end_date)
2186
+ ##########################################################
2187
+ ActiveRecord::Base.connection.execute <<~SQL
2188
+ DROP FUNCTION IF EXISTS patient_date_enrolled;
2189
+ SQL
2190
+
2191
+ ::Drug.arv_drugs.map(&:concept_id)
2192
+
2193
+ ActiveRecord::Base.connection.execute <<~SQL
2194
+ CREATE FUNCTION patient_date_enrolled(my_patient_id int) RETURNS DATE
2195
+ DETERMINISTIC
2196
+ BEGIN
2197
+ DECLARE my_start_date DATE;
2198
+ DECLARE min_start_date DATETIME;
2199
+ DECLARE arv_concept_id INT(11);
2200
+
2201
+ SET arv_concept_id = (SELECT concept_id FROM concept_name WHERE name ='ANTIRETROVIRAL DRUGS' LIMIT 1);
2202
+
2203
+ 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);
2204
+
2205
+
2206
+ RETURN my_start_date;
2207
+ END;
2208
+ SQL
2209
+ ##########################################################
2210
+
2211
+ ActiveRecord::Base.connection.execute <<~SQL
2212
+ DROP TABLE IF EXISTS `temp_earliest_start_date`;
2213
+ SQL
2214
+
2215
+ ActiveRecord::Base.connection.execute <<~SQL
2216
+ CREATE TABLE temp_earliest_start_date
2217
+ select
2218
+ `p`.`patient_id` AS `patient_id`,
2219
+ `pe`.`gender` AS `gender`,
2220
+ `pe`.`birthdate`,
2221
+ date_antiretrovirals_started(`p`.`patient_id`, min(`s`.`start_date`)) AS `earliest_start_date`,
2222
+ cast(patient_date_enrolled(`p`.`patient_id`) as date) AS `date_enrolled`,
2223
+ `person`.`death_date` AS `death_date`,
2224
+ (select timestampdiff(year, `pe`.`birthdate`, min(`s`.`start_date`))) AS `age_at_initiation`,
2225
+ (select timestampdiff(day, `pe`.`birthdate`, min(`s`.`start_date`))) AS `age_in_days`
2226
+ from
2227
+ ((`patient_program` `p`
2228
+ left join `person` `pe` ON ((`pe`.`person_id` = `p`.`patient_id`))
2229
+ left join `patient_state` `s` ON ((`p`.`patient_program_id` = `s`.`patient_program_id`)))
2230
+ left join `person` ON ((`person`.`person_id` = `p`.`patient_id`)))
2231
+ where
2232
+ ((`p`.`voided` = 0)
2233
+ and (`s`.`voided` = 0)
2234
+ and (`p`.`program_id` = 1)
2235
+ and (`s`.`state` = 7))
2236
+ group by `p`.`patient_id`;
2237
+ SQL
2238
+ end
2239
+
2240
+ def arv_orders
2241
+ ::Order.joins(:drug_order).where(
2242
+ 'drug_order.drug_inventory_id in (?)', ::Drug.arv_drugs.collect(&:drug_id)
2243
+ ).order(:start_date)
2244
+ end
2245
+
2246
+ def add_patient_record(person, order, cohort_struct)
2247
+ date_enrolled = order.start_date.to_date
2248
+ art_earliest_start_date = patient_earliest_start_date(order.patient_id, date_enrolled)
2249
+
2250
+ if date_enrolled == art_earliest_start_date
2251
+ cohort_struct.cum_initiated_on_art_first_time ||= 0
2252
+ cohort_struct.cum_initiated_on_art_first_time += 1
2253
+ end
2254
+
2255
+ age_in_months_when_starting = (art_earliest_start_date - person.birthdate).to_i
2256
+ age_when_starting = (age_in_months_when_starting / 365).to_i
2257
+ deathdate = person.death_date ? "'#{person.death_date.to_date}'" : 'NULL'
2258
+
2259
+ ActiveRecord::Base.connection.execute(
2260
+ "INSERT INTO temp_earliest_start_date (
2261
+ patient_id,
2262
+ date_enrolled,
2263
+ earliest_start_date,
2264
+ gender,
2265
+ birthdate,
2266
+ birthdate_estimated,
2267
+ death_date,
2268
+ age_at_initiation,
2269
+ age_in_days
2270
+ ) VALUES (
2271
+ #{order.patient_id},
2272
+ '#{order.start_date.to_date}',
2273
+ '#{art_earliest_start_date.to_date}',
2274
+ '#{person.gender}',
2275
+ '#{person.birthdate}',
2276
+ '#{person.birthdate_estimated}',
2277
+ #{deathdate},
2278
+ '#{age_when_starting}',
2279
+ '#{age_in_months_when_starting}'
2280
+ )"
2281
+ )
2282
+ end
2283
+
2284
+ # Retrieve the earliest (clinic?) start date for a patient
2285
+ def patient_earliest_start_date(patient_id, min_start_date)
2286
+ result = ActiveRecord::Base.connection.select_one(
2287
+ "SELECT date_antiretrovirals_started(
2288
+ #{patient_id}, '#{min_start_date.to_date}'
2289
+ ) AS date"
2290
+ )
2291
+ result['date'].to_date
2292
+ end
2293
+
2294
+ # Returns a list of reasons for starting ART for each patient.
2295
+ def patients_art_start_reason(patient_ids)
2296
+ ActiveRecord::Base.connection.execute(
2297
+ "SELECT person_id as patient_id, name, obs_datetime
2298
+ FROM reason_for_art_eligibility_obs
2299
+ WHERE #{in_manager(column: 'person_id',
2300
+ values: patient_ids)} #{site_manager(operator: 'AND',
2301
+ column: 'reason_for_art_eligibility_obs.site_id', location: @location)}"
2302
+ )
2303
+ end
2304
+
2305
+ def patient_death_date(patient)
2306
+ ::PatientState.find_by(program: program('HIV PROGRAM'), patient:)
2307
+ end
2308
+
2309
+ # Filter out patients with given start causes from patient_ids
2310
+ def filter_patients_with_start_cause(patient_ids, start_cause_concept_ids)
2311
+ obs_concepts = start_cause_concept_ids.push(concept('WHO STAGES CRITERIA PRESENT').concept_id)
2312
+ obs_values = start_cause_concept_ids.push(concept('YES').concept_id)
2313
+
2314
+ ActiveRecord::Base.connection.execute(
2315
+ "SELECT *
2316
+ FROM hiv_staging_conditions_obs
2317
+ WHERE #{in_manager(column: 'concept_id', values: obs_concepts)}
2318
+ AND #{in_manager(column: 'value_coded', values: obs_values)}
2319
+ AND #{in_manager(column: 'person_id',
2320
+ values: patient_ids)} #{site_manager(operator: 'AND',
2321
+ column: 'hiv_staging_conditions_obs.site_id', location: @location)}
2322
+ GROUP BY person_id"
2323
+ )
2324
+ end
2325
+
2326
+ def patient_in_program?(patient)
2327
+ return false unless patient
2328
+
2329
+ pprogram = ::PatientProgram.find_by(program: program('HIV PROGRAM'), patient:)
2330
+ return false unless pprogram
2331
+
2332
+ ::PatientState.where(patient_program: pprogram, state: 7).exists?
2333
+ end
2334
+ end
2335
+ end
2336
+ end
2337
+ # rubocop:enable Metrics/ClassLength