malawi_hiv_program_reports 1.0.1 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/app/services/malawi_hiv_program_reports/README.md +16 -0
  3. data/app/services/malawi_hiv_program_reports/adapters/moh/custom.rb +199 -0
  4. data/app/services/malawi_hiv_program_reports/archiving_candidates.rb +130 -0
  5. data/app/services/malawi_hiv_program_reports/arv_refill_periods.rb +311 -0
  6. data/app/services/malawi_hiv_program_reports/clinic/README.md +5 -0
  7. data/app/services/malawi_hiv_program_reports/clinic/appointments_report.rb +317 -0
  8. data/app/services/malawi_hiv_program_reports/clinic/discrepancy_report.rb +42 -0
  9. data/app/services/malawi_hiv_program_reports/clinic/docs/hypertension_report.md +31 -0
  10. data/app/services/malawi_hiv_program_reports/clinic/drug_dispensations.rb +48 -0
  11. data/app/services/malawi_hiv_program_reports/clinic/external_consultation_clients.rb +69 -0
  12. data/app/services/malawi_hiv_program_reports/clinic/hypertension_report.rb +223 -0
  13. data/app/services/malawi_hiv_program_reports/clinic/ipt_coverage.rb +112 -0
  14. data/app/services/malawi_hiv_program_reports/clinic/ipt_report.rb +69 -0
  15. data/app/services/malawi_hiv_program_reports/clinic/lims_results.rb +55 -0
  16. data/app/services/malawi_hiv_program_reports/clinic/outcome_list.rb +127 -0
  17. data/app/services/malawi_hiv_program_reports/clinic/patients_alive_and_on_treatment.rb +57 -0
  18. data/app/services/malawi_hiv_program_reports/clinic/patients_due_for_viral_load.rb +39 -0
  19. data/app/services/malawi_hiv_program_reports/clinic/patients_on_antiretrovirals.rb +44 -0
  20. data/app/services/malawi_hiv_program_reports/clinic/patients_on_dtg.rb +36 -0
  21. data/app/services/malawi_hiv_program_reports/clinic/patients_on_treatment.rb +42 -0
  22. data/app/services/malawi_hiv_program_reports/clinic/patients_with_outdated_demographics.rb +173 -0
  23. data/app/services/malawi_hiv_program_reports/clinic/pregnant_patients.rb +91 -0
  24. data/app/services/malawi_hiv_program_reports/clinic/regimen_dispensation_data.rb +282 -0
  25. data/app/services/malawi_hiv_program_reports/clinic/regimen_switch.rb +456 -0
  26. data/app/services/malawi_hiv_program_reports/clinic/regimens_and_formulations.rb +182 -0
  27. data/app/services/malawi_hiv_program_reports/clinic/regimens_by_weight_and_gender.rb +108 -0
  28. data/app/services/malawi_hiv_program_reports/clinic/retention.rb +246 -0
  29. data/app/services/malawi_hiv_program_reports/clinic/stock_card_report.rb +65 -0
  30. data/app/services/malawi_hiv_program_reports/clinic/tpt_outcome.rb +494 -0
  31. data/app/services/malawi_hiv_program_reports/clinic/tx_rtt.rb +169 -0
  32. data/app/services/malawi_hiv_program_reports/clinic/viral_load.rb +292 -0
  33. data/app/services/malawi_hiv_program_reports/clinic/viral_load_disaggregated.rb +97 -0
  34. data/app/services/malawi_hiv_program_reports/clinic/viral_load_results.rb +175 -0
  35. data/app/services/malawi_hiv_program_reports/clinic/visits_report.rb +113 -0
  36. data/app/services/malawi_hiv_program_reports/clinic/vl_collection.rb +48 -0
  37. data/app/services/malawi_hiv_program_reports/cohort/outcomes.rb +338 -0
  38. data/app/services/malawi_hiv_program_reports/cohort/regimens.rb +69 -0
  39. data/app/services/malawi_hiv_program_reports/cohort/side_effects.rb +141 -0
  40. data/app/services/malawi_hiv_program_reports/cohort/tpt.rb +172 -0
  41. data/app/services/malawi_hiv_program_reports/moh/cohort.rb +278 -0
  42. data/app/services/malawi_hiv_program_reports/moh/cohort_builder.rb +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