malawi_hiv_program_reports 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/app/services/malawi_hiv_program_reports/README.md +16 -0
  3. data/app/services/malawi_hiv_program_reports/adapters/moh/custom.rb +199 -0
  4. data/app/services/malawi_hiv_program_reports/archiving_candidates.rb +130 -0
  5. data/app/services/malawi_hiv_program_reports/arv_refill_periods.rb +311 -0
  6. data/app/services/malawi_hiv_program_reports/clinic/README.md +5 -0
  7. data/app/services/malawi_hiv_program_reports/clinic/appointments_report.rb +317 -0
  8. data/app/services/malawi_hiv_program_reports/clinic/discrepancy_report.rb +42 -0
  9. data/app/services/malawi_hiv_program_reports/clinic/docs/hypertension_report.md +31 -0
  10. data/app/services/malawi_hiv_program_reports/clinic/drug_dispensations.rb +48 -0
  11. data/app/services/malawi_hiv_program_reports/clinic/external_consultation_clients.rb +69 -0
  12. data/app/services/malawi_hiv_program_reports/clinic/hypertension_report.rb +223 -0
  13. data/app/services/malawi_hiv_program_reports/clinic/ipt_coverage.rb +112 -0
  14. data/app/services/malawi_hiv_program_reports/clinic/ipt_report.rb +69 -0
  15. data/app/services/malawi_hiv_program_reports/clinic/lims_results.rb +55 -0
  16. data/app/services/malawi_hiv_program_reports/clinic/outcome_list.rb +127 -0
  17. data/app/services/malawi_hiv_program_reports/clinic/patients_alive_and_on_treatment.rb +57 -0
  18. data/app/services/malawi_hiv_program_reports/clinic/patients_due_for_viral_load.rb +39 -0
  19. data/app/services/malawi_hiv_program_reports/clinic/patients_on_antiretrovirals.rb +44 -0
  20. data/app/services/malawi_hiv_program_reports/clinic/patients_on_dtg.rb +36 -0
  21. data/app/services/malawi_hiv_program_reports/clinic/patients_on_treatment.rb +42 -0
  22. data/app/services/malawi_hiv_program_reports/clinic/patients_with_outdated_demographics.rb +173 -0
  23. data/app/services/malawi_hiv_program_reports/clinic/pregnant_patients.rb +91 -0
  24. data/app/services/malawi_hiv_program_reports/clinic/regimen_dispensation_data.rb +282 -0
  25. data/app/services/malawi_hiv_program_reports/clinic/regimen_switch.rb +456 -0
  26. data/app/services/malawi_hiv_program_reports/clinic/regimens_and_formulations.rb +182 -0
  27. data/app/services/malawi_hiv_program_reports/clinic/regimens_by_weight_and_gender.rb +108 -0
  28. data/app/services/malawi_hiv_program_reports/clinic/retention.rb +246 -0
  29. data/app/services/malawi_hiv_program_reports/clinic/stock_card_report.rb +65 -0
  30. data/app/services/malawi_hiv_program_reports/clinic/tpt_outcome.rb +494 -0
  31. data/app/services/malawi_hiv_program_reports/clinic/tx_rtt.rb +169 -0
  32. data/app/services/malawi_hiv_program_reports/clinic/viral_load.rb +292 -0
  33. data/app/services/malawi_hiv_program_reports/clinic/viral_load_disaggregated.rb +97 -0
  34. data/app/services/malawi_hiv_program_reports/clinic/viral_load_results.rb +175 -0
  35. data/app/services/malawi_hiv_program_reports/clinic/visits_report.rb +113 -0
  36. data/app/services/malawi_hiv_program_reports/clinic/vl_collection.rb +48 -0
  37. data/app/services/malawi_hiv_program_reports/cohort/outcomes.rb +338 -0
  38. data/app/services/malawi_hiv_program_reports/cohort/regimens.rb +69 -0
  39. data/app/services/malawi_hiv_program_reports/cohort/side_effects.rb +141 -0
  40. data/app/services/malawi_hiv_program_reports/cohort/tpt.rb +172 -0
  41. data/app/services/malawi_hiv_program_reports/moh/cohort.rb +278 -0
  42. data/app/services/malawi_hiv_program_reports/moh/cohort_builder.rb +2340 -0
  43. data/app/services/malawi_hiv_program_reports/moh/cohort_disaggregated.rb +608 -0
  44. data/app/services/malawi_hiv_program_reports/moh/cohort_disaggregated_additions.rb +208 -0
  45. data/app/services/malawi_hiv_program_reports/moh/cohort_disaggregated_builder.rb +526 -0
  46. data/app/services/malawi_hiv_program_reports/moh/cohort_struct.rb +219 -0
  47. data/app/services/malawi_hiv_program_reports/moh/cohort_survival_analysis.rb +203 -0
  48. data/app/services/malawi_hiv_program_reports/moh/moh_tpt.rb +223 -0
  49. data/app/services/malawi_hiv_program_reports/moh/tpt_newly_initiated.rb +235 -0
  50. data/app/services/malawi_hiv_program_reports/pepfar/defaulter_list.rb +25 -0
  51. data/app/services/malawi_hiv_program_reports/pepfar/maternal_status.rb +29 -0
  52. data/app/services/malawi_hiv_program_reports/pepfar/patient_start_vl.rb +45 -0
  53. data/app/services/malawi_hiv_program_reports/pepfar/regimen_switch.rb +479 -0
  54. data/app/services/malawi_hiv_program_reports/pepfar/sc_arvdisp.rb +174 -0
  55. data/app/services/malawi_hiv_program_reports/pepfar/sc_curr.rb +98 -0
  56. data/app/services/malawi_hiv_program_reports/pepfar/tb_prev.rb +163 -0
  57. data/app/services/malawi_hiv_program_reports/pepfar/tb_prev2.rb +222 -0
  58. data/app/services/malawi_hiv_program_reports/pepfar/tb_prev3.rb +421 -0
  59. data/app/services/malawi_hiv_program_reports/pepfar/tpt_status.rb +181 -0
  60. data/app/services/malawi_hiv_program_reports/pepfar/tx_ml.rb +181 -0
  61. data/app/services/malawi_hiv_program_reports/pepfar/tx_new.rb +202 -0
  62. data/app/services/malawi_hiv_program_reports/pepfar/tx_rtt.rb +288 -0
  63. data/app/services/malawi_hiv_program_reports/pepfar/tx_tb.rb +283 -0
  64. data/app/services/malawi_hiv_program_reports/pepfar/utils.rb +141 -0
  65. data/app/services/malawi_hiv_program_reports/pepfar/viral_load_coverage.rb +414 -0
  66. data/app/services/malawi_hiv_program_reports/pepfar/viral_load_coverage2.rb +433 -0
  67. data/app/services/malawi_hiv_program_reports/report_map.rb +56 -0
  68. data/app/services/malawi_hiv_program_reports/utils/README.md +8 -0
  69. data/app/services/malawi_hiv_program_reports/utils/common_sql_query_utils.rb +60 -0
  70. data/app/services/malawi_hiv_program_reports/utils/concurrency_utils.rb +53 -0
  71. data/app/services/malawi_hiv_program_reports/utils/docs/common_sql_query_utils.md +53 -0
  72. data/app/services/malawi_hiv_program_reports/utils/model_utils.rb +66 -0
  73. data/app/services/malawi_hiv_program_reports/utils/parameter_utils.rb +32 -0
  74. data/app/services/malawi_hiv_program_reports/utils/time_utils.rb +52 -0
  75. data/lib/malawi_hiv_program_reports/version.rb +1 -1
  76. metadata +74 -1
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Report for Showing all HIV Viral Load Tests Done Or Sample Collected per the specified period
4
+ module MalawiHivProgramReports
5
+ module Clinic
6
+ class VlCollection
7
+ include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
8
+ def initialize(start_date:, end_date:, **kwargs)
9
+ @start_date = start_date.to_date.beginning_of_day
10
+ @end_date = end_date.to_date.end_of_day
11
+ @occupation = kwargs[:occupation]
12
+ end
13
+
14
+ def find_report
15
+ data
16
+ end
17
+
18
+ private
19
+
20
+ def data
21
+ ActiveRecord::Base.connection.select_all <<~SQL
22
+ SELECT
23
+ o.person_id AS patient_id,
24
+ pn.given_name,
25
+ pn.family_name,
26
+ p.gender,
27
+ p.birthdate,
28
+ i.identifier AS identifier,
29
+ DATE(ord.start_date) AS order_date
30
+ FROM obs o
31
+ INNER JOIN orders ord ON ord.order_id = o.order_id AND ord.voided = 0
32
+ INNER JOIN person p ON p.person_id = o.person_id AND p.voided = 0
33
+ INNER JOIN person_name pn ON pn.person_id = p.person_id AND pn.voided = 0
34
+ LEFT JOIN patient_identifier i ON i.patient_id = p.person_id AND i.voided = 0 AND i.identifier_type = #{indetifier_type}
35
+ LEFT JOIN (#{current_occupation_query}) AS a ON a.person_id = p.person_id
36
+ WHERE o.concept_id = 9737 -- Test Type
37
+ AND o.value_coded = 856 -- Viral Load
38
+ AND o.obs_datetime BETWEEN '#{@start_date}' AND '#{@end_date}'
39
+ AND o.voided = 0 #{%w[Military Civilian].include?(@occupation) ? 'AND' : ''} #{occupation_filter(occupation: @occupation, field_name: 'value', table_name: 'a', include_clause: false)}
40
+ SQL
41
+ end
42
+
43
+ def indetifier_type
44
+ @indetifier_type ||= ::PatientIdentifierType.find_by_name!(::GlobalPropertyService.use_filing_numbers? ? 'Filing Number' : 'ARV Number').id
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,338 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MalawiHivProgramReports
4
+ module Cohort
5
+ class Outcomes
6
+ include MalawiHivProgramReports::Adapters::Moh::Custom
7
+ include MalawiHivProgramReports::Utils::ModelUtils
8
+ attr_reader :end_date
9
+
10
+ def initialize(end_date:, definition: 'moh', location: nil)
11
+ definition = definition.downcase
12
+ raise ::ArgumentError, "Invalid outcomes definition: #{definition}" unless %w[moh pepfar].include?(definition)
13
+
14
+ @adapter = ActiveRecord::Base.connection.adapter_name.downcase
15
+ @end_date = end_date.to_date
16
+ @definition = definition
17
+ @location = location
18
+ end
19
+
20
+ def update_cummulative_outcomes
21
+ initialize_table
22
+
23
+ # HIC SUNT DRACONIS: The order of the operations below matters,
24
+ # do not change it unless you know what you are doing!!!
25
+ load_patients_who_died
26
+ load_patients_who_stopped_treatment
27
+ load_patients_on_pre_art
28
+ load_patients_without_state
29
+ load_patients_without_drug_orders
30
+ load_patients_on_treatment
31
+ load_defaulters
32
+ end
33
+
34
+ private
35
+
36
+ def arv_drugs_concept_set
37
+ @arv_drugs_concept_set ||= ::ConceptSet.where(set: concept_name('Antiretroviral drugs').concept)
38
+ .select(:concept_id)
39
+ end
40
+
41
+ def drug_order_type
42
+ @drug_order_type ||= order_type('Drug order')
43
+ end
44
+
45
+ def program_states(*names)
46
+ ::ProgramWorkflowState.joins(:program_workflow)
47
+ .joins(:concept)
48
+ .merge(::ProgramWorkflow.where(program: hiv_program))
49
+ .merge(::Concept.joins(:concept_names)
50
+ .merge(::ConceptName.where(name: names)))
51
+ .select(:program_workflow_state_id)
52
+ end
53
+
54
+ def hiv_program
55
+ @hiv_program ||= program('HIV Program')
56
+ end
57
+
58
+ def initialize_table
59
+ ActiveRecord::Base.connection.execute <<~SQL
60
+ DROP TABLE IF EXISTS temp_patient_outcomes
61
+ SQL
62
+
63
+ ActiveRecord::Base.connection.execute <<~SQL
64
+ CREATE TABLE temp_patient_outcomes (
65
+ patient_id INT NOT NULL,
66
+ cum_outcome VARCHAR(120) NOT NULL,
67
+ outcome_date DATE DEFAULT NULL,
68
+ site_id INT NOT NULL,
69
+ PRIMARY KEY (patient_id, site_id)
70
+ )
71
+ SQL
72
+
73
+ ActiveRecord::Base.connection.execute <<~SQL
74
+ CREATE INDEX tpo_outcomes ON temp_patient_outcomes (patient_id, cum_outcome, outcome_date, site_id)
75
+ SQL
76
+ end
77
+
78
+ # Loads all patiens with an outcome of died as of given date
79
+ # into the temp_patient_outcomes table.
80
+ def load_patients_who_died
81
+ date = ActiveRecord::Base.connection.quote(end_date)
82
+
83
+ ActiveRecord::Base.connection.execute <<~SQL
84
+ INSERT INTO temp_patient_outcomes
85
+ SELECT patients.patient_id, 'Patient died', MIN(patient_state.start_date), #{@location}
86
+ FROM temp_earliest_start_date AS patients
87
+ INNER JOIN patient_program
88
+ ON patient_program.patient_id = patients.patient_id
89
+ AND patient_program.program_id = #{hiv_program.program_id}
90
+ AND patient_program.voided = 0 #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
91
+ INNER JOIN patient_state
92
+ ON patient_state.patient_program_id = patient_program.patient_program_id
93
+ AND patient_state.state = (#{program_states('Patient died').limit(1).to_sql})
94
+ AND patient_state.start_date < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
95
+ AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
96
+ WHERE patients.date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
97
+ GROUP BY patients.patient_id
98
+ SQL
99
+ end
100
+
101
+ # Loads all patients with an outcome of transferred out or
102
+ # treatment stopped into temp_patient_outcomes table.
103
+ def load_patients_who_stopped_treatment
104
+ date = ActiveRecord::Base.connection.quote(end_date)
105
+
106
+ ActiveRecord::Base.connection.execute <<~SQL
107
+ INSERT INTO temp_patient_outcomes
108
+ SELECT patients.patient_id,
109
+ (
110
+ SELECT name FROM concept_name
111
+ WHERE concept_id = (
112
+ SELECT concept_id FROM program_workflow_state
113
+ WHERE program_workflow_state_id = patient_state.state
114
+ LIMIT 1
115
+ )
116
+ ) AS cum_outcome,
117
+ patient_state.start_date, #{@location}
118
+ FROM temp_earliest_start_date AS patients
119
+ INNER JOIN patient_program
120
+ ON patient_program.patient_id = patients.patient_id
121
+ AND patient_program.program_id = #{hiv_program.program_id}
122
+ AND patient_program.voided = 0 #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
123
+ INNER JOIN patient_state
124
+ ON patient_state.patient_program_id = patient_program.patient_program_id
125
+ AND patient_state.state IN (#{program_states('Patient transferred out', 'Treatment stopped').to_sql})
126
+ AND patient_state.start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
127
+ AND (patient_state.end_date >= #{date} OR patient_state.end_date IS NULL)
128
+ AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
129
+ INNER JOIN (
130
+ SELECT patient_program_id, MAX(start_date) AS start_date
131
+ FROM patient_state
132
+ WHERE patient_state.start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
133
+ AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
134
+ GROUP BY patient_program_id
135
+ ) AS max_patient_state
136
+ ON max_patient_state.patient_program_id = patient_state.patient_program_id
137
+ AND max_patient_state.start_date = patient_state.start_date #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
138
+ WHERE patients.date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
139
+ AND patients.patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
140
+ GROUP BY patients.patient_id #{@adapter == 'mysql2' ? '' : ',patient_state.state, patient_state.start_date'}
141
+ SQL
142
+ end
143
+
144
+ # Load all patients on Pre-ART.
145
+ def load_patients_on_pre_art
146
+ date = ActiveRecord::Base.connection.quote(end_date)
147
+
148
+ ActiveRecord::Base.connection.execute <<~SQL
149
+ INSERT INTO temp_patient_outcomes
150
+ SELECT patients.patient_id,
151
+ CASE
152
+ WHEN #{current_defaulter_function('patients.patient_id')} = 1 THEN 'Defaulted'
153
+ ELSE 'Pre-ART (Continue)'
154
+ END AS cum_outcome,
155
+ patient_state.start_date, #{@location}
156
+ FROM temp_earliest_start_date AS patients
157
+ INNER JOIN patient_program
158
+ ON patient_program.patient_id = patients.patient_id
159
+ AND patient_program.program_id = #{hiv_program.program_id}
160
+ AND patient_program.voided = 0 #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
161
+ INNER JOIN patient_state
162
+ ON patient_state.patient_program_id = patient_program.patient_program_id
163
+ AND patient_state.state = (#{program_states('Pre-ART (Continue)').limit(1).to_sql})
164
+ AND patient_state.start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
165
+ AND (patient_state.end_date >= #{date} OR patient_state.end_date IS NULL)
166
+ AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
167
+ INNER JOIN (
168
+ SELECT patient_program_id, MAX(start_date) AS start_date
169
+ FROM patient_state
170
+ WHERE patient_state.start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
171
+ AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
172
+ GROUP BY patient_program_id
173
+ ) AS max_patient_state
174
+ ON max_patient_state.patient_program_id = patient_state.patient_program_id
175
+ AND max_patient_state.start_date = patient_state.start_date #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
176
+ WHERE patients.date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
177
+ AND patients.patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
178
+ GROUP BY patients.patient_id #{@adapter == 'mysql2' ? '' : ',patient_state.start_date'}
179
+ SQL
180
+ end
181
+
182
+ # Load all patients without a state
183
+ def load_patients_without_state
184
+ date = ActiveRecord::Base.connection.quote(end_date)
185
+
186
+ ActiveRecord::Base.connection.execute <<~SQL
187
+ INSERT INTO temp_patient_outcomes
188
+ SELECT patients.patient_id,
189
+ CASE
190
+ WHEN #{current_defaulter_function('patients.patient_id')} = 1 THEN 'Defaulted'
191
+ ELSE 'Unknown'
192
+ END AS cum_outcome,
193
+ NULL, #{@location} as site_id
194
+ FROM temp_earliest_start_date AS patients
195
+ INNER JOIN patient_program
196
+ ON patient_program.patient_id = patients.patient_id
197
+ AND patient_program.program_id = #{hiv_program.program_id}
198
+ AND patient_program.voided = 0 #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
199
+ WHERE patients.date_enrolled <= #{date}
200
+ AND patient_program.patient_program_id NOT IN (
201
+ SELECT patient_program_id
202
+ FROM patient_state
203
+ WHERE start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
204
+ AND voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
205
+ )
206
+ AND patients.patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'patient_program.site_id', location: @location)} )
207
+ GROUP BY patients.patient_id
208
+ SQL
209
+ end
210
+
211
+ # Load all patients without drug orders or have drug orders
212
+ # without a quantity.
213
+ def load_patients_without_drug_orders
214
+ date = ActiveRecord::Base.connection.quote(end_date)
215
+
216
+ ActiveRecord::Base.connection.execute <<~SQL
217
+ INSERT INTO temp_patient_outcomes
218
+ SELECT patients.patient_id,
219
+ 'Unknown',
220
+ NULL, #{@location}
221
+ FROM temp_earliest_start_date AS patients
222
+ WHERE date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
223
+ AND patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
224
+ AND patient_id NOT IN (
225
+ SELECT patient_id
226
+ FROM orders
227
+ LEFT JOIN drug_order ON orders.order_id = drug_order.order_id #{site_manager(operator: 'AND', column: 'drug_order.site_id', location: @location)}
228
+ WHERE start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
229
+ AND quantity > 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
230
+ AND order_type_id = #{drug_order_type.order_type_id}
231
+ AND concept_id IN (#{arv_drugs_concept_set.to_sql})
232
+ )
233
+ SQL
234
+ end
235
+
236
+ # Loads all patients who are on treatment
237
+ def load_patients_on_treatment
238
+ date = ActiveRecord::Base.connection.quote(end_date)
239
+
240
+ ActiveRecord::Base.connection.execute <<~SQL
241
+ INSERT INTO temp_patient_outcomes
242
+ SELECT patients.patient_id, 'On antiretrovirals', patient_state.start_date, #{@location}
243
+ FROM temp_earliest_start_date AS patients
244
+ INNER JOIN patient_program
245
+ ON patient_program.patient_id = patients.patient_id
246
+ AND patient_program.program_id = #{hiv_program.program_id}
247
+ AND patient_program.voided = 0 #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
248
+ /* Get patients' `on ARV` states that are before given date */
249
+ INNER JOIN patient_state
250
+ ON patient_state.patient_program_id = patient_program.patient_program_id
251
+ AND patient_state.state = (#{program_states('On antiretrovirals').limit(1).to_sql})
252
+ AND patient_state.start_date < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
253
+ AND (patient_state.end_date >= #{date} OR patient_state.end_date IS NULL)
254
+ AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
255
+ /* Select only the most recent state out of those retrieved above */
256
+ INNER JOIN (
257
+ SELECT patient_program_id, MAX(start_date) AS start_date
258
+ FROM patient_state
259
+ WHERE patient_state.start_date < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
260
+ AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
261
+ GROUP BY patient_program_id
262
+ ) AS max_patient_state
263
+ ON max_patient_state.patient_program_id = patient_state.patient_program_id
264
+ AND max_patient_state.start_date = patient_state.start_date #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
265
+ /* HACK: Ensure that the states captured above do correspond have corresponding
266
+ ARV dispensations. In other words filter out any `on ARVs` states whose
267
+ dispensation's may have been voided or states that were created manually
268
+ without any drugs being dispensed. */
269
+ INNER JOIN (
270
+ SELECT orders.patient_id, MIN(orders.auto_expire_date) AS auto_expire_date
271
+ FROM orders
272
+ INNER JOIN drug_order ON orders.order_id = drug_order.order_id #{site_manager(operator: 'AND', column: 'drug_order.site_id', location: @location)}
273
+ INNER JOIN (
274
+ SELECT patient_id, MAX(start_date) AS start_date
275
+ FROM orders
276
+ INNER JOIN drug_order ON orders.order_id = drug_order.order_id #{site_manager(operator: 'AND', column: 'drug_order.site_id', location: @location)} AND drug_order.quantity > 0
277
+ WHERE order_type_id = #{drug_order_type.order_type_id}
278
+ AND concept_id IN (#{arv_drugs_concept_set.to_sql})
279
+ AND orders.start_date < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
280
+ AND voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
281
+ AND patient_id IN (SELECT patient_id FROM temp_earliest_start_date #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
282
+ AND patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
283
+ GROUP BY patient_id
284
+ ) AS max_drug_orders
285
+ ON max_drug_orders.patient_id = orders.patient_id
286
+ AND max_drug_orders.start_date = orders.start_date #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
287
+ WHERE order_type_id = #{drug_order_type.order_type_id}
288
+ AND concept_id IN (#{arv_drugs_concept_set.to_sql})
289
+ AND orders.start_date < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
290
+ AND quantity > 0
291
+ AND voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
292
+ AND orders.patient_id IN (SELECT patient_id FROM temp_earliest_start_date #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)})
293
+ AND orders.patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)})
294
+ GROUP BY orders.patient_id
295
+ ) AS first_order_to_expire
296
+ ON (first_order_to_expire.auto_expire_date >= #{date} OR #{timestampdiff_manager(date1: 'DATE(first_order_to_expire.auto_expire_date)', date2: date, interval: 'DAY')} <= #{@definition == 'pepfar' ? 28 : 56})
297
+ AND first_order_to_expire.patient_id = patient_program.patient_id #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
298
+ WHERE patients.date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
299
+ AND patients.patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
300
+ GROUP BY patients.patient_id #{@adapter == 'mysql2' ? '' : ',patient_state.start_date'}
301
+ SQL
302
+ end
303
+
304
+ # Load defaulters
305
+ def load_defaulters
306
+ date = ActiveRecord::Base.connection.quote(end_date)
307
+
308
+ ActiveRecord::Base.connection.execute <<~SQL
309
+ INSERT INTO temp_patient_outcomes
310
+ SELECT patient_id, #{patient_outcome_function('patient_id')}, NULL, #{@location}
311
+ FROM temp_earliest_start_date
312
+ WHERE date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
313
+ AND patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
314
+ SQL
315
+ end
316
+
317
+ def current_defaulter_function(sql_column)
318
+ defaulter_function = @definition == 'moh' ? 'current_defaulter' : 'current_pepfar_defaulter'
319
+ quoted_end_date = ActiveRecord::Base.connection.quote(end_date)
320
+ parameters = "#{sql_column}, #{quoted_end_date}, #{@location}"
321
+ function_manager(function: defaulter_function, location: @location, args: parameters)
322
+ rescue StandardError => e
323
+ Rails.logger.error "Error: #{e.message}"
324
+ raise "Invalid outcomes definition: #{@definition}"
325
+ end
326
+
327
+ def patient_outcome_function(sql_column)
328
+ outcome_function = @definition == 'moh' ? 'patient_outcome' : 'pepfar_patient_outcome'
329
+ quoted_end_date = ActiveRecord::Base.connection.quote(end_date)
330
+ parameters = "#{sql_column}, #{quoted_end_date}, #{@location}"
331
+ function_manager(function: outcome_function, location: @location, args: parameters)
332
+ rescue StandardError => e
333
+ Rails.logger.error "Error: #{e.message}"
334
+ raise "Invalid outcomes definition: #{@definition}"
335
+ end
336
+ end
337
+ end
338
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MalawiHivProgramReports
4
+ module Cohort
5
+ class Regimens
6
+ include MalawiHivProgramReports::Utils::ModelUtils
7
+ include MalawiHivProgramReports::Adapters::Moh::Custom
8
+
9
+ def patient_regimens(date, location)
10
+ @location = location
11
+ @adapter = ActiveRecord::Base.connection.adapter_name.downcase
12
+ date = ActiveRecord::Base.connection.quote(date)
13
+
14
+ ActiveRecord::Base.connection.select_all <<~SQL
15
+ SELECT prescriptions.patient_id,
16
+ regimens.name AS regimen_category,
17
+ prescriptions.drugs,
18
+ prescriptions.prescription_date
19
+ FROM (
20
+ SELECT orders.patient_id,
21
+ #{@adapter == 'mysql2' ? 'GROUP_CONCAT(DISTINCT drug_order.drug_inventory_id ORDER BY drug_order.drug_inventory_id ASC) AS drugs,' : "(SELECT STRING_AGG(drug_inventory_id::VARCHAR, ',') FROM (SELECT DISTINCT drug_order.drug_inventory_id FROM drug_order ORDER BY drug_order.drug_inventory_id ASC) AS subquery) AS drugs,"}
22
+ recent_prescription.prescription_date
23
+ FROM temp_patient_outcomes AS outcomes
24
+ INNER JOIN orders
25
+ ON orders.patient_id = outcomes.patient_id
26
+ AND orders.concept_id IN (#{arv_drugs_concept_set.to_sql})
27
+ AND orders.voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
28
+ INNER JOIN drug_order
29
+ ON drug_order.order_id = orders.order_id AND drug_order.quantity > 0 #{site_manager(operator: 'AND', column: 'drug_order.site_id', location: @location)}
30
+ /* Only select drugs prescribed on the last prescription day */
31
+ INNER JOIN (
32
+ SELECT patient_id, DATE(MAX(start_date)) AS prescription_date
33
+ FROM orders
34
+ INNER JOIN drug_order
35
+ ON drug_order.order_id = orders.order_id
36
+ AND drug_order.quantity > 0 #{site_manager(operator: 'AND', column: 'drug_order.site_id', location: @location)}
37
+ WHERE orders.voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
38
+ AND orders.concept_id IN (#{arv_drugs_concept_set.to_sql})
39
+ AND orders.start_date < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
40
+ AND orders.patient_id IN (
41
+ SELECT patient_id FROM temp_patient_outcomes WHERE cum_outcome = 'On antiretrovirals' #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
42
+ )
43
+ GROUP BY orders.patient_id
44
+ ) AS recent_prescription
45
+ ON recent_prescription.patient_id = orders.patient_id
46
+ AND orders.start_date
47
+ BETWEEN recent_prescription.prescription_date
48
+ AND #{interval_manager(date: 'recent_prescription.prescription_date', value: 1, interval: 'DAY', operator: '+')}
49
+ GROUP BY orders.patient_id #{@adapter == 'mysql2' ? '' : ', recent_prescription.prescription_date'}
50
+ ) AS prescriptions
51
+ LEFT JOIN (
52
+ SELECT #{@adapter == 'mysql2' ? 'GROUP_CONCAT(drug.drug_id ORDER BY drug.drug_id ASC) AS drugs,' : "(SELECT STRING_AGG(drug_id::VARCHAR, ',') FROM (SELECT drug.drug_id FROM drug ORDER BY drug.drug_id ASC) AS subquery) AS drugs,"}
53
+ regimen_name.name AS name
54
+ FROM moh_regimen_combination AS combo
55
+ INNER JOIN moh_regimen_combination_drug AS drug USING (regimen_combination_id)
56
+ INNER JOIN moh_regimen_name AS regimen_name USING (regimen_name_id)
57
+ GROUP BY combo.regimen_combination_id, regimen_name.name#{' '}
58
+ ) AS regimens
59
+ ON regimens.drugs = prescriptions.drugs
60
+ SQL
61
+ end
62
+
63
+ def arv_drugs_concept_set
64
+ @arv_drugs_concept_set ||= ::ConceptSet.where(set: concept_name('Antiretroviral drugs'))
65
+ .select(:concept_id)
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MalawiHivProgramReports
4
+ module Cohort
5
+ class SideEffects
6
+ include MalawiHivProgramReports::Utils::ModelUtils
7
+ include MalawiHivProgramReports::Adapters::Moh::Custom
8
+
9
+ def update_side_effects(date, location)
10
+ @location = location
11
+ @adapter = ActiveRecord::Base.connection.adapter_name.downcase
12
+ initialize_table
13
+
14
+ load_patients_with_side_effects(date)
15
+ load_patients_without_side_effects(date)
16
+ load_patients_missing_side_effects(date)
17
+ end
18
+
19
+ def initialize_table
20
+ ActiveRecord::Base.connection.execute <<~SQL
21
+ DROP TABLE IF EXISTS temp_patient_side_effects
22
+ SQL
23
+
24
+ ActiveRecord::Base.connection.execute <<~SQL
25
+ CREATE TABLE temp_patient_side_effects (
26
+ patient_id INT PRIMARY KEY,
27
+ has_se VARCHAR(120) NOT NULL,
28
+ site_id INT DEFAULT NULL
29
+ )
30
+ SQL
31
+ ActiveRecord::Base.connection.execute 'CREATE INDEX tpse_site_id ON temp_patient_side_effects(site_id)'
32
+ ActiveRecord::Base.connection.execute 'CREATE INDEX idx_se ON temp_patient_side_effects(has_se)'
33
+ ActiveRecord::Base.connection.execute <<~SQL
34
+ CREATE INDEX idx_side_effects ON temp_patient_side_effects (patient_id, has_se, site_id)
35
+ SQL
36
+ end
37
+
38
+ def load_patients_with_side_effects(date)
39
+ date = ActiveRecord::Base.connection.quote(date)
40
+
41
+ ActiveRecord::Base.connection.execute <<~SQL
42
+ INSERT INTO temp_patient_side_effects(patient_id, has_se, site_id)
43
+ SELECT patients.patient_id, 'Yes', #{@location}
44
+ FROM temp_earliest_start_date AS patients
45
+ INNER JOIN temp_patient_outcomes
46
+ ON temp_patient_outcomes.patient_id = patients.patient_id
47
+ AND LOWER(temp_patient_outcomes.cum_outcome) = LOWER('On antiretrovirals') #{site_manager(operator: 'AND', column: 'temp_patient_outcomes.site_id', location: @location)}
48
+ INNER JOIN obs AS side_effects_group
49
+ ON side_effects_group.person_id = patients.patient_id
50
+ AND side_effects_group.concept_id = #{art_side_effects.concept_id}
51
+ AND side_effects_group.voided = 0 #{site_manager(operator: 'AND', column: 'side_effects_group.site_id', location: @location)}
52
+ /* Limit check to last visit before #{date} */
53
+ INNER JOIN (
54
+ SELECT person_id, MAX(obs_datetime) AS obs_datetime FROM obs
55
+ WHERE concept_id = #{art_side_effects.concept_id}
56
+ /* Side effects on initial visit are treated as contra-indications */
57
+ AND obs_datetime < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
58
+ AND voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
59
+ AND person_id IN (SELECT patient_id FROM temp_patient_outcomes WHERE LOWER(cum_outcome) = LOWER('On antiretrovirals') #{site_manager(operator: 'AND', column: 'site_id', location: @location)})
60
+ GROUP BY person_id
61
+ ) AS last_visit
62
+ ON last_visit.person_id = side_effects_group.person_id
63
+ AND last_visit.obs_datetime = side_effects_group.obs_datetime
64
+ AND last_visit.obs_datetime >= #{interval_manager(date: 'patients.date_enrolled', value: 1, interval: 'DAY', operator: '+')}
65
+ INNER JOIN obs AS side_effects
66
+ ON side_effects.person_id = patients.patient_id
67
+ AND side_effects_group.obs_id = side_effects.obs_group_id
68
+ AND side_effects.value_coded = #{yes.concept_id}
69
+ AND side_effects.voided = 0 #{site_manager(operator: 'AND', column: 'side_effects.site_id', location: @location)}
70
+ WHERE patients.date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
71
+ GROUP BY patients.patient_id
72
+ SQL
73
+ end
74
+
75
+ def load_patients_without_side_effects(date)
76
+ date = ActiveRecord::Base.connection.quote(date)
77
+
78
+ ActiveRecord::Base.connection.execute <<~SQL
79
+ INSERT INTO temp_patient_side_effects
80
+ SELECT patients.patient_id, 'No', #{@location}
81
+ FROM temp_earliest_start_date AS patients
82
+ INNER JOIN temp_patient_outcomes
83
+ ON temp_patient_outcomes.patient_id = patients.patient_id
84
+ AND LOWER(temp_patient_outcomes.cum_outcome) = LOWER('On antiretrovirals') #{site_manager(operator: 'AND', column: 'temp_patient_outcomes.site_id', location: @location)}
85
+ INNER JOIN obs AS side_effects_group
86
+ ON side_effects_group.person_id = patients.patient_id
87
+ AND side_effects_group.concept_id = #{art_side_effects.concept_id}
88
+ AND side_effects_group.voided = 0 #{site_manager(operator: 'AND', column: 'side_effects_group.site_id', location: @location)}
89
+ /* Limit check to last visit before #{date} */
90
+ INNER JOIN (
91
+ SELECT person_id, MAX(obs_datetime) AS obs_datetime FROM obs
92
+ WHERE concept_id = #{art_side_effects.concept_id}
93
+ /* Side effects on initial visit are treated as contra-indications */
94
+ AND obs_datetime < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
95
+ AND voided = 0 #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
96
+ AND person_id IN (
97
+ SELECT patient_id FROM temp_patient_outcomes WHERE LOWER(cum_outcome) = LOWER('On antiretrovirals') #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
98
+ )
99
+ GROUP BY person_id
100
+ ) AS last_visit
101
+ ON last_visit.person_id = side_effects_group.person_id
102
+ AND last_visit.obs_datetime = side_effects_group.obs_datetime
103
+ AND last_visit.obs_datetime >= #{interval_manager(date: 'patients.date_enrolled', value: 1, interval: 'DAY', operator: '+')}
104
+ INNER JOIN obs AS side_effects
105
+ ON side_effects.person_id = patients.patient_id
106
+ AND side_effects_group.obs_id = side_effects.obs_group_id
107
+ AND side_effects.value_coded = #{no.concept_id}
108
+ AND side_effects.voided = 0 #{site_manager(operator: 'AND', column: 'side_effects.site_id', location: @location)}
109
+ WHERE patients.date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
110
+ AND patients.patient_id NOT IN (
111
+ SELECT patient_id FROM temp_patient_side_effects WHERE has_se = 'Yes' #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
112
+ )
113
+ GROUP BY patients.patient_id
114
+ SQL
115
+ end
116
+
117
+ def load_patients_missing_side_effects(date)
118
+ date = ActiveRecord::Base.connection.quote(date)
119
+
120
+ ActiveRecord::Base.connection.execute <<~SQL
121
+ INSERT INTO temp_patient_side_effects
122
+ SELECT patient_id, 'Unknown', #{@location} FROM temp_earliest_start_date
123
+ WHERE date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
124
+ AND patient_id NOT IN (SELECT patient_id FROM temp_patient_side_effects #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)})
125
+ SQL
126
+ end
127
+
128
+ def yes
129
+ @yes ||= concept_name('Yes')
130
+ end
131
+
132
+ def no
133
+ @no ||= concept_name('No')
134
+ end
135
+
136
+ def art_side_effects
137
+ @art_side_effects ||= concept_name('Malawi ART Side Effects')
138
+ end
139
+ end
140
+ end
141
+ end