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