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,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