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,456 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MalawiHivProgramReports
4
+ module Clinic
5
+ class RegimenSwitch
6
+ def initialize(start_date:, end_date:, **kwargs)
7
+ @start_date = start_date
8
+ @end_date = end_date
9
+ @occupation = kwargs[:occupation]
10
+ end
11
+
12
+ def regimen_switch(pepfar)
13
+ swicth_report(pepfar)
14
+ end
15
+
16
+ def regimen_report(type)
17
+ RegimenDispensationData.new(type:, start_date: @start_date,
18
+ end_date: @end_date, occupation: @occupation)
19
+ .find_report
20
+ end
21
+
22
+ def latest_regimen_dispensed(rebuild_outcome)
23
+ if rebuild_outcome || @occupation.present?
24
+ MalawiHivProgramReports::Moh::CohortBuilder.new(outcomes_definition: 'moh')
25
+ .init_temporary_tables(@start_date, @end_date, @occupation)
26
+ end
27
+
28
+ latest_regimens
29
+ end
30
+
31
+ private
32
+
33
+ def latest_regimens
34
+ pills_dispensed = ::ConceptName.find_by_name('Amount of drug dispensed').concept_id
35
+ patient_identifier_type = ::PatientIdentifierType.find_by_name('ARV Number').id
36
+
37
+ arv_dispensentions = ActiveRecord::Base.connection.select_all <<~SQL
38
+ SELECT
39
+ o.patient_id, drug.drug_id, o.order_id, i.identifier,
40
+ drug.name, d.quantity, o.start_date, obs.value_numeric,
41
+ person.birthdate, person.gender
42
+ FROM orders o
43
+ INNER JOIN drug_order d ON d.order_id = o.order_id AND d.quantity > 0
44
+ INNER JOIN drug ON drug.drug_id = d.drug_inventory_id
45
+ INNER JOIN arv_drug On arv_drug.drug_id = drug.drug_id
46
+ INNER JOIN temp_patient_outcomes t ON o.patient_id = t.patient_id AND t.cum_outcome = 'On antiretrovirals'
47
+ INNER JOIN person ON person.person_id = o.patient_id AND person.voided = 0
48
+ INNER JOIN (
49
+ SELECT MAX(o.start_date) start_date, o.patient_id
50
+ FROM orders o
51
+ INNER JOIN drug_order dor ON dor.order_id = o.order_id AND dor.quantity > 0
52
+ AND dor.drug_inventory_id IN (SELECT drug_id FROM arv_drug)
53
+ WHERE o.voided = 0
54
+ AND o.start_date <= '#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
55
+ AND o.start_date >= '#{@start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
56
+ GROUP BY o.patient_id
57
+ ) lor ON lor.start_date = o.start_date AND lor.patient_id = o.patient_id
58
+ LEFT JOIN obs on obs.order_id = o.order_id AND obs.concept_id=#{pills_dispensed} AND obs.voided = 0
59
+ LEFT JOIN patient_identifier i ON i.patient_id = o.patient_id
60
+ AND i.identifier_type = #{patient_identifier_type} AND i.voided = 0
61
+ WHERE o.voided = 0
62
+ AND o.start_date <= '#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
63
+ AND o.start_date >= '#{@start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
64
+ ORDER BY o.patient_id
65
+ SQL
66
+
67
+ patient_list = arv_dispensentions.map { |d| d['patient_id'] }.uniq.push(0)
68
+
69
+ @latest_vl = latest_vl_orders(patient_list)
70
+ @latest_result = latest_vl_results(patient_list)
71
+
72
+ formated_data = {}
73
+
74
+ (arv_dispensentions || []).each do |data|
75
+ dispensation_date = data['start_date'].to_date
76
+ patient_id = data['patient_id'].to_i
77
+ order_id = data['order_id'].to_i
78
+ # drug_id = data['drug_id'].to_i
79
+ medication = data['name']
80
+ quantity = data['quantity'].to_f
81
+ value_numeric = data['value_numeric'].to_f
82
+ drug_id = data['drug_id'].to_i
83
+ # find the latest vl result for the patient from the array of vl results {patient_id: number, order_date: date}
84
+ latest_vl = @latest_vl.select { |vl| vl['patient_id'] == patient_id }&.first
85
+ latest_result = @latest_result.select { |vl| vl['patient_id'] == patient_id }&.first
86
+
87
+ formated_data[patient_id] = {} if formated_data[patient_id].blank?
88
+ if formated_data[patient_id][order_id].blank?
89
+ formated_data[patient_id][order_id] = {
90
+ name: medication,
91
+ quantity:,
92
+ dispensation_date:,
93
+ identifier: data['identifier'],
94
+ gender: data['gender'],
95
+ birthdate: data['birthdate'],
96
+ drug_id:,
97
+ pack_sizes: [],
98
+ vl_latest_order_date: latest_vl.present? ? latest_vl['order_date']&.to_date : 'N/A',
99
+ vl_latest_result_date: latest_result.present? ? latest_result['result_date']&.to_date : 'N/A',
100
+ vl_latest_result: latest_result.present? ? latest_result['result'] : 'N/A'
101
+ }
102
+ end
103
+
104
+ formated_data[patient_id][order_id][:pack_sizes] << value_numeric
105
+ end
106
+
107
+ formated_data
108
+ end
109
+
110
+ def regimen_data
111
+ ::EncounterType.find_by_name('DISPENSING').id
112
+ arv_concept_id = ::ConceptName.find_by_name('Antiretroviral drugs').concept_id
113
+
114
+ drug_ids = ::Drug.joins('INNER JOIN concept_set s ON s.concept_id = drug.concept_id')
115
+ .where('s.concept_set = ?', arv_concept_id).map(&:drug_id)
116
+
117
+ ActiveRecord::Base.connection.execute('drop table if exists tmp_latest_arv_dispensation ;')
118
+
119
+ ActiveRecord::Base.connection.execute("
120
+ create table tmp_latest_arv_dispensation
121
+ SELECT patient_id,DATE(MAX(start_date)) as start_date
122
+ FROM orders INNER JOIN drug_order t USING (order_id)
123
+ WHERE
124
+ (
125
+ start_date BETWEEN '#{@start_date.to_date.strftime('%Y-%m-%d 00:00:00')}' AND '#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
126
+ AND t.drug_inventory_id IN (#{drug_ids.join(',')}) AND t.quantity > 0
127
+ )
128
+ group by patient_id")
129
+
130
+ ActiveRecord::Base.connection.execute('create index lad_patient_id_and_start_date on tmp_latest_arv_dispensation (start_date, patient_id);')
131
+
132
+ arv_dispensentions = ActiveRecord::Base.connection.select_all <<~SQL
133
+ SELECT
134
+ o.patient_id patient_id, o.start_date, o.order_id,
135
+ d.quantity, drug.name
136
+ FROM orders o
137
+ INNER JOIN drug_order d ON o.order_id = d.order_id
138
+ INNER JOIN drug ON d.drug_inventory_id = drug.drug_id
139
+ INNER JOIN tmp_latest_arv_dispensation k on (o.patient_id = k.patient_id and DATE(o.start_date) = k.start_date)
140
+ WHERE d.drug_inventory_id IN(#{drug_ids.join(',')})
141
+ AND d.quantity > 0 AND o.voided = 0 AND o.start_date BETWEEN '#{@start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
142
+ AND '#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}' GROUP BY o.order_id;
143
+ SQL
144
+
145
+ patient_ids = []
146
+ (arv_dispensentions || []).each do |data|
147
+ patient_ids << data['patient_id'].to_i
148
+ end
149
+ return [] if patient_ids.blank?
150
+
151
+ ActiveRecord::Base.connection.select_all <<~SQL
152
+ SELECT
153
+ `p`.`patient_id` AS `patient_id`,
154
+ cast(patient_date_enrolled(`p`.`patient_id`) as date) AS `date_enrolled`,
155
+ date_antiretrovirals_started(`p`.`patient_id`, min(`s`.`start_date`)) AS `earliest_start_date`
156
+ FROM
157
+ ((`patient_program` `p`
158
+ LEFT JOIN `person` `pe` ON ((`pe`.`person_id` = `p`.`patient_id`))
159
+ LEFT JOIN `patient_state` `s` ON ((`p`.`patient_program_id` = `s`.`patient_program_id`)))
160
+ LEFT JOIN `person` ON ((`person`.`person_id` = `p`.`patient_id`)))
161
+ WHERE
162
+ ((`p`.`voided` = 0)
163
+ AND (`s`.`voided` = 0)
164
+ AND (`p`.`program_id` = 1)
165
+ AND (`s`.`state` = 7))
166
+ AND (`s`.`start_date` <= '#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
167
+ AND p.patient_id IN(#{patient_ids.join(',')}))
168
+ GROUP BY `p`.`patient_id`;
169
+ SQL
170
+ end
171
+
172
+ def arv_dispensention_data(patient_id)
173
+ ::EncounterType.find_by_name('DISPENSING').id
174
+ arv_concept_id = ::ConceptName.find_by_name('Antiretroviral drugs').concept_id
175
+
176
+ drug_ids = ::Drug.joins('INNER JOIN concept_set s ON s.concept_id = drug.concept_id')
177
+ .where('s.concept_set = ?', arv_concept_id).map(&:drug_id)
178
+
179
+ ActiveRecord::Base.connection.select_all <<~SQL
180
+ SELECT
181
+ o.patient_id, drug.name, d.quantity, o.start_date
182
+ FROM orders o
183
+ INNER JOIN drug_order d ON d.order_id = o.order_id
184
+ INNER JOIN drug ON drug.drug_id = d.drug_inventory_id
185
+ WHERE d.drug_inventory_id IN(#{drug_ids.join(',')})
186
+ AND o.patient_id = #{patient_id} AND
187
+ d.quantity > 0 AND o.voided = 0 AND DATE(o.start_date) = (
188
+ SELECT DATE(MAX(start_date)) FROM orders
189
+ INNER JOIN drug_order t USING(order_id)
190
+ WHERE patient_id = o.patient_id
191
+ AND (
192
+ start_date BETWEEN '#{@start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
193
+ AND '#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
194
+ AND t.drug_inventory_id IN(#{drug_ids.join(',')}) AND quantity > 0
195
+ )
196
+ ) GROUP BY (o.order_id)
197
+ SQL
198
+ end
199
+
200
+ def current_regimen(type)
201
+ data = regimen_data
202
+
203
+ clients = {}
204
+ (data || []).each do |r|
205
+ patient_id = r['patient_id'].to_i
206
+
207
+ outcome_status = if type == 'pepfar'
208
+ ActiveRecord::Base.connection.select_one <<~SQL
209
+ SELECT pepfar_patient_outcome(#{patient_id}, '#{@end_date.to_date}') outcome;
210
+ SQL
211
+
212
+ else
213
+ ActiveRecord::Base.connection.select_one <<~SQL
214
+ SELECT patient_outcome(#{patient_id}, '#{@end_date.to_date}') outcome;
215
+ SQL
216
+
217
+ end
218
+ next unless outcome_status['outcome'] == 'On antiretrovirals'
219
+
220
+ medications = arv_dispensention_data(patient_id)
221
+
222
+ begin
223
+ visit_date = medications.first['start_date'].to_date
224
+ rescue StandardError
225
+ next
226
+ end
227
+
228
+ curr_reg = ActiveRecord::Base.connection.select_one <<~SQL
229
+ SELECT patient_current_regimen(#{patient_id}, '#{@end_date.to_date}') current_regimen
230
+ SQL
231
+
232
+ next unless visit_date >= @start_date.to_date && visit_date <= @end_date.to_date
233
+
234
+ if clients[patient_id].blank?
235
+ demo = ActiveRecord::Base.connection.select_one <<~SQL
236
+ SELECT
237
+ p.birthdate, p.gender, i.identifier arv_number,
238
+ n.given_name, n.family_name
239
+ FROM person p
240
+ LEFT JOIN person_name n ON n.person_id = p.person_id AND n.voided = 0
241
+ LEFT JOIN patient_identifier i ON i.patient_id = p.person_id
242
+ AND i.identifier_type = 4 AND i.voided = 0
243
+ WHERE p.person_id = #{patient_id} GROUP BY p.person_id
244
+ ORDER BY n.date_created DESC, i.date_created DESC;
245
+ SQL
246
+
247
+ viral_load = latest_vl_results([patient_id])
248
+ clients[patient_id] = {
249
+ arv_number: demo['arv_number'],
250
+ given_name: demo['given_name'],
251
+ family_name: demo['family_name'],
252
+ birthdate: demo['birthdate'],
253
+ gender: demo['gender'] == 'M' ? 'M' : maternal_status(patient_id, demo['gender']),
254
+ current_regimen: curr_reg['current_regimen'],
255
+ current_weight: current_weight(patient_id),
256
+ art_start_date: r['earliest_start_date'],
257
+ medication: [],
258
+ vl_result: viral_load ? viral_load['result'] : nil,
259
+ vl_result_date: viral_load ? viral_load['result_date'] : nil
260
+ }
261
+ end
262
+
263
+ (medications || []).each do |med|
264
+ clients[patient_id][:medication] << {
265
+ medication: med['name'],
266
+ quantity: med['quantity'],
267
+ start_date: visit_date
268
+ }
269
+ end
270
+ end
271
+
272
+ clients
273
+ end
274
+
275
+ def swicth_report(pepfar)
276
+ clients = {}
277
+ data = regimen_data
278
+ pepfar_outcome_builder(pepfar.blank? ? 'moh' : 'pepfar')
279
+
280
+ (data || []).each do |r|
281
+ patient_id = r['patient_id'].to_i
282
+ medications = arv_dispensention_data(patient_id)
283
+
284
+ outcome_status = ActiveRecord::Base.connection.select_one <<~SQL
285
+ SELECT cum_outcome FROM temp_patient_outcomes WHERE patient_id = #{patient_id};
286
+ SQL
287
+
288
+ next if outcome_status.blank?
289
+ next if outcome_status['cum_outcome'].blank?
290
+ next unless outcome_status['cum_outcome'] == 'On antiretrovirals'
291
+
292
+ visit_date = medications.first['start_date']
293
+ visit_date.blank? ? next : (visit_date = visit_date.to_date)
294
+
295
+ next unless visit_date >= @start_date.to_date && visit_date <= @end_date.to_date
296
+
297
+ prev_reg = ActiveRecord::Base.connection.select_one <<~SQL
298
+ SELECT patient_current_regimen(#{patient_id}, '#{(visit_date - 1.day).to_date}') previous_regimen
299
+ SQL
300
+
301
+ current_reg = ActiveRecord::Base.connection.select_one <<~SQL
302
+ SELECT patient_current_regimen(#{patient_id}, '#{visit_date}') current_regimen
303
+ SQL
304
+
305
+ next if prev_reg['previous_regimen'] == current_reg['current_regimen']
306
+ next if prev_reg['previous_regimen'] == 'N/A'
307
+
308
+ if clients[patient_id].blank?
309
+ demo = ActiveRecord::Base.connection.select_one <<~SQL
310
+ SELECT
311
+ p.birthdate, p.gender, i.identifier arv_number,
312
+ n.given_name, n.family_name, p.person_id
313
+ FROM person p
314
+ LEFT JOIN person_name n ON n.person_id = p.person_id AND n.voided = 0
315
+ LEFT JOIN patient_identifier i ON i.patient_id = p.person_id
316
+ AND i.identifier_type = 4 AND i.voided = 0
317
+ WHERE p.person_id = #{patient_id} GROUP BY p.person_id
318
+ ORDER BY n.date_created DESC, i.date_created DESC
319
+ SQL
320
+
321
+ clients[patient_id] = {
322
+ arv_number: (demo['arv_number'].blank? ? 'N/A' : demo['arv_number']),
323
+ given_name: demo['given_name'],
324
+ family_name: demo['family_name'],
325
+ birthdate: demo['birthdate'],
326
+ gender: demo['gender'],
327
+ previous_regimen: prev_reg['previous_regimen'],
328
+ current_regimen: current_reg['current_regimen'],
329
+ patient_type: get_patient_type(demo['person_id'], pepfar),
330
+ current_weight: current_weight(demo['person_id']),
331
+ art_start_date: r['earliest_start_date'],
332
+ medication: []
333
+ }
334
+ end
335
+
336
+ (medications || []).each do |m|
337
+ clients[patient_id][:medication] << {
338
+ medication: m['name'], quantity: m['quantity'],
339
+ start_date: visit_date
340
+ }
341
+ end
342
+ end
343
+
344
+ clients
345
+ end
346
+
347
+ def get_patient_type(patient_id, pepfar)
348
+ return nil unless pepfar
349
+
350
+ concept_id = ::ConceptName.find_by_name('Type of patient').concept_id
351
+ ext_id = ::ConceptName.find_by_name('External consultation').concept_id
352
+ obs = ::Observation.where(concept_id:, value_coded: ext_id, person_id: patient_id)
353
+ (obs.blank? ? 'Resident' : 'External')
354
+ end
355
+
356
+ def pepfar_outcome_builder(repport_type = 'moh')
357
+ cohort_builder = MalawiHivProgramReports::Moh::CohortDisaggregated.new(name: 'Regimen switch', type: repport_type,
358
+ start_date: @start_date.to_date,
359
+ end_date: @end_date.to_date, rebuild: true,
360
+ occupation: @occupation)
361
+ cohort_builder.rebuild_outcomes(repport_type)
362
+ end
363
+
364
+ def current_weight(patient_id)
365
+ weight_concept = ::ConceptName.find_by_name('Weight (kg)').concept_id
366
+ obs = ::Observation.where("person_id = ? AND concept_id = ?
367
+ AND obs_datetime <= ? AND (value_numeric IS NOT NULL OR value_text IS NOT NULL)",
368
+ patient_id, weight_concept, @end_date.to_date.strftime('%Y-%m-%d 23:59:59'))
369
+ .order('obs_datetime DESC, date_created DESC')
370
+
371
+ return nil if obs.blank?
372
+
373
+ (obs.first.value_numeric.blank? ? obs.first.value_text : obs.first.value_numeric)
374
+ end
375
+
376
+ # def vl_result(patient_id)
377
+ # ActiveRecord::Base.connection.select_one <<~SQL
378
+ # SELECT lab_result_obs.obs_datetime AS result_date,
379
+ # CONCAT (COALESCE(measure.value_modifier, '='),' ',COALESCE(measure.value_numeric, measure.value_text, '')) as result
380
+ # FROM obs AS lab_result_obs
381
+ # INNER JOIN orders
382
+ # ON orders.order_id = lab_result_obs.order_id
383
+ # AND orders.voided = 0
384
+ # INNER JOIN obs AS measure
385
+ # ON measure.obs_group_id = lab_result_obs.obs_id
386
+ # AND measure.voided = 0
387
+ # INNER JOIN (
388
+ # SELECT concept_id, name
389
+ # FROM concept_name
390
+ # INNER JOIN concept USING (concept_id)
391
+ # WHERE concept.retired = 0
392
+ # AND name NOT LIKE 'Lab test result'
393
+ # GROUP BY concept_id
394
+ # ) AS measure_concept
395
+ # ON measure_concept.concept_id = measure.concept_id
396
+ # WHERE lab_result_obs.voided = 0
397
+ # AND measure.person_id = #{patient_id}
398
+ # AND (measure.value_numeric IS NOT NULL || measure.value_text IS NOT NULL)
399
+ # AND lab_result_obs.obs_datetime <= '#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
400
+ # ORDER BY lab_result_obs.obs_datetime DESC
401
+ # LIMIT 1
402
+ # SQL
403
+ # end
404
+
405
+ def latest_vl_orders(patient_list)
406
+ ActiveRecord::Base.connection.select_all <<~SQL
407
+ SELECT odr.patient_id, MAX(start_date) AS order_date
408
+ FROM obs o
409
+ INNER JOIN orders odr ON odr.order_id = o.order_id AND odr.voided = 0 AND DATE(odr.start_date) <= '#{@end_date}'
410
+ WHERE o.concept_id = #{::ConceptName.find_by_name('Test Type').concept_id}
411
+ AND o.value_coded = #{::ConceptName.find_by_name('HIV viral load').concept_id}
412
+ AND o.voided = 0
413
+ AND o.person_id IN (#{patient_list.join(',')})
414
+ GROUP BY odr.patient_id
415
+ SQL
416
+ end
417
+
418
+ def latest_vl_results(patient_list)
419
+ ActiveRecord::Base.connection.select_all <<~SQL
420
+ SELECT o.person_id AS patient_id,
421
+ o.obs_datetime AS result_date,
422
+ CONCAT (COALESCE(o.value_modifier, '='),' ',COALESCE(o.value_numeric, o.value_text, '')) AS result
423
+ FROM obs o
424
+ INNER JOIN (
425
+ SELECT MAX(obs_datetime) AS obs_datetime, person_id
426
+ FROM obs co
427
+ INNER JOIN orders odr ON odr.order_id = co.order_id AND odr.voided = 0
428
+ WHERE co.concept_id = #{::ConceptName.find_by_name('HIV viral load').concept_id}
429
+ AND co.voided = 0
430
+ AND co.obs_datetime <= '#{@end_date}'
431
+ AND (co.value_numeric IS NOT NULL || co.value_text IS NOT NULL)
432
+ AND co.person_id IN (#{patient_list.join(',')})
433
+ GROUP BY co.person_id
434
+ ) AS latest_vl ON latest_vl.obs_datetime = o.obs_datetime AND latest_vl.person_id = o.person_id
435
+ INNER JOIN orders odr ON odr.order_id = o.order_id AND odr.voided = 0
436
+ WHERE o.concept_id = #{::ConceptName.find_by_name('HIV viral load').concept_id}
437
+ AND o.voided = 0 AND o.obs_datetime <= '#{@end_date}'
438
+ AND (o.value_numeric IS NOT NULL || o.value_text IS NOT NULL)
439
+ AND o.person_id IN (#{patient_list.join(',')})
440
+ ORDER BY o.obs_datetime DESC
441
+ SQL
442
+ end
443
+
444
+ def maternal_status(patient_id, current_gender)
445
+ return nil if current_gender.blank?
446
+
447
+ result = MalawiHivProgramReports::Pepfar::ViralLoadCoverage2.new(start_date: @start_date,
448
+ end_date: @end_date).vl_maternal_status([patient_id])
449
+ gender = 'FNP'
450
+ gender = 'FP' unless result[:FP].blank?
451
+ gender = 'FBf' unless result[:FBf].blank?
452
+ gender
453
+ end
454
+ end
455
+ end
456
+ end
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Retrieve patients in a particular regimen and formulation.
4
+ module MalawiHivProgramReports
5
+ module Clinic
6
+ class RegimensAndFormulations
7
+ include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
8
+
9
+ attr_reader :start_date, :end_date, :regimen, :formulation
10
+
11
+ def initialize(start_date:, end_date:, regimen: nil, formulation: 'tablets', **kwargs)
12
+ raise ::InvalidParameterError, 'regimen is required' unless regimen
13
+
14
+ unless %w[granules tablets pellets].include?(formulation)
15
+ raise ::InvalidParameterError, "Invalid formalation: #{formulation}"
16
+ end
17
+
18
+ @start_date = start_date.to_date
19
+ @end_date = end_date.to_date
20
+ @formulation = formulation
21
+ @regimen = regimen
22
+ @occupation = kwargs[:occupation]
23
+ end
24
+
25
+ def find_report
26
+ patients
27
+ end
28
+
29
+ def patients
30
+ patients_with_prescriptions.each_with_object([]) do |patient, matching_patients|
31
+ prescribed_drugs = drugs_prescribed_to_patient(patient.patient_id, patient.prescription_date).map(&:drug_id)
32
+ non_matching_drugs = Set.new(drugs) - prescribed_drugs
33
+
34
+ next unless non_matching_drugs.empty?
35
+
36
+ demographics = patient_demographics(patient.patient_id, patient.prescription_date)
37
+
38
+ matching_patients << {
39
+ patient_id: demographics.patient_id,
40
+ arv_number: demographics.arv_number,
41
+ birthdate: demographics.birthdate,
42
+ gender: demographics.gender,
43
+ weight: demographics.weight
44
+ }
45
+ end
46
+ end
47
+
48
+ private
49
+
50
+ TABLET_REGIMENS = {
51
+ '0A' => [969, 22],
52
+ '0P' => [1044, 968],
53
+ '2A' => [731],
54
+ '2P' => [732],
55
+ '4A' => [39, 11],
56
+ '4P' => [736, 30],
57
+ '5A' => [735],
58
+ '6A' => [734, 22],
59
+ '7A' => [734, 932],
60
+ '8A' => [39, 932],
61
+ '9A' => [969, 73],
62
+ '9P' => [1044, 74],
63
+ '10A' => [734, 73],
64
+ '11A' => [39, 73],
65
+ '11P' => [736, 74],
66
+ '12A' => [982, 977, 976],
67
+ '13A' => [983],
68
+ '14A' => [984, 982],
69
+ '14P' => [736, 982],
70
+ '15A' => [969, 982],
71
+ '15P' => [1044, 982],
72
+ '16A' => [969, 954],
73
+ '16P' => [1044, 1043],
74
+ '17A' => [969, 11],
75
+ '17P' => [1044, 30]
76
+ }.freeze
77
+
78
+ GRANULES_REGIMENS = {
79
+ '9P' => [1044, 1045],
80
+ '11P' => [736, 1045]
81
+ }.freeze
82
+
83
+ PELLETS_REGIMENS = {
84
+ '9P' => [1044, 979],
85
+ '11P' => [736, 979]
86
+ }.freeze
87
+
88
+ REGIMENS_BY_FORMULATION = {
89
+ 'granules' => GRANULES_REGIMENS,
90
+ 'pellets' => PELLETS_REGIMENS,
91
+ 'tablets' => TABLET_REGIMENS
92
+ }.freeze
93
+
94
+ # Returns drugs in selected regimen and formulation
95
+ def drugs
96
+ REGIMENS_BY_FORMULATION[formulation][regimen]
97
+ end
98
+
99
+ def patients_with_prescriptions
100
+ return [] if drugs.nil?
101
+
102
+ ::DrugOrder.select('orders.patient_id AS patient_id, MAX(start_date) AS prescription_date')
103
+ .joins(:order)
104
+ .joins("LEFT JOIN (#{current_occupation_query}) AS a ON a.person_id = orders.patient_id")
105
+ .where(quantity: 1..Float::INFINITY, drug_inventory_id: drugs)
106
+ .where(occupation_filter(occupation: @occupation, field_name: 'value', table_name: 'a',
107
+ include_clause: false).to_s)
108
+ .merge(treatment_orders)
109
+ .group('orders.patient_id')
110
+ end
111
+
112
+ # Returns all orders in treatment encounter of HIV PROGRAM
113
+ def treatment_orders
114
+ ::Order.joins(:encounter)
115
+ .where(start_date: start_date..end_date)
116
+ .merge(treatment_encounter)
117
+ .or(::Order.joins(:encounter)
118
+ .where(auto_expire_date: start_date..end_date)
119
+ .merge(treatment_encounter))
120
+ .or(::Order.joins(:encounter)
121
+ .where('start_date < ? AND auto_expire_date > ?', start_date, end_date)
122
+ .merge(treatment_encounter))
123
+ end
124
+
125
+ def treatment_encounter
126
+ ::Encounter.where(encounter_type: ::EncounterType.find_by_name('Treatment'),
127
+ program_id: Constants::PROGRAM_ID)
128
+ end
129
+
130
+ # Returns drugs prescribed to patient on given day
131
+ def drugs_prescribed_to_patient(patient_id, prescription_date)
132
+ ::DrugOrder.select('drug_order.drug_inventory_id AS drug_id')
133
+ .joins(:order)
134
+ .where(quantity: 1..Float::INFINITY, drug_inventory_id: drugs)
135
+ .merge(::Order.joins(:encounter)
136
+ .where(patient_id:, start_date: prescription_date)
137
+ .merge(treatment_encounter))
138
+ end
139
+
140
+ def patient_demographics(patient_id, prescription_date)
141
+ ::Person.find_by_sql(
142
+ <<~SQL
143
+ SELECT person.person_id AS patient_id, patient_identifier.identifier AS arv_number,
144
+ person.birthdate AS birthdate, person.gender AS gender, obs.value_numeric AS weight
145
+ FROM person
146
+ LEFT JOIN patient_identifier ON patient_identifier.patient_id = person.person_id
147
+ AND patient_identifier.identifier_type = #{arv_number_type_id}
148
+ AND patient_identifier.voided = 0
149
+ LEFT JOIN obs ON obs.person_id = person.person_id
150
+ AND obs.concept_id = #{weight_concept_id}
151
+ AND obs.obs_datetime = (
152
+ SELECT MAX(obs_datetime) FROM obs
153
+ WHERE person_id = #{patient_id}
154
+ AND concept_id = #{weight_concept_id}
155
+ AND obs_datetime <= '#{prescription_date}'
156
+ AND voided = 0
157
+ ) AND obs.voided = 0
158
+ WHERE person.person_id = #{patient_id}
159
+ SQL
160
+ ).first
161
+ end
162
+
163
+ def patient_recent_weight(patient_id, as_of)
164
+ ::Observation.select(:value_numeric)
165
+ .where(concept_id: ::ConceptName.find_by_name('Weight (kg)').concept_id,
166
+ person_id: patient_id)
167
+ .where('obs_datetime < ? AND value_numeric IS NOT NULL', as_of)
168
+ .order(obs_datetime: :desc)
169
+ .first
170
+ &.value_numeric
171
+ end
172
+
173
+ def weight_concept_id
174
+ @weight_concept_id ||= ::ConceptName.find_by_name('Weight (kg)').concept_id
175
+ end
176
+
177
+ def arv_number_type_id
178
+ @arv_number_type_id ||= ::PatientIdentifierType.find_by_name('ARV Number').id
179
+ end
180
+ end
181
+ end
182
+ end