malawi_hiv_program_reports 1.0.1 → 1.0.3

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 +2337 -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 +205 -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,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MalawiHivProgramReports
4
+ module Pepfar
5
+ # This class is used to generate the SC_CURR report
6
+ # The current number of ARV drug units (bottles) at the end of the reporting period by ARV drug category
7
+ class ScCurr
8
+ DRUG_CATEGORY = {
9
+ 'TLD 30-count bottles' => { drugs: [983], quantity: 30 },
10
+ 'TLD 90-count bottles' => { drugs: [983], quantity: 90 },
11
+ 'TLD 180-count bottles' => { drugs: [983], quantity: 180 },
12
+ 'TLE/400 30-count bottles' => { drugs: [735], quantity: 30 },
13
+ 'TLE/400 90-count bottles' => { drugs: [735], quantity: 90 },
14
+ 'TLE 600/TEE bottles' => { drugs: [11], quantity: 'N/A' },
15
+ 'DTG 10 90-count bottles' => { drugs: [980], quantity: 90 },
16
+ 'DTG 50 30-count bottles' => { drugs: [982], quantity: 30 },
17
+ 'LPV/r 100/25 tabs 60 tabs/bottle' => { drugs: [23, 73, 74, 739, 977, 1045], quantity: 60 },
18
+ 'LPV/r 40/10 (pediatrics) bottles' => { drugs: [94, 979], quantity: 'N/A' },
19
+ 'NVP (adult) bottles' => { drugs: [22, 613], quantity: 'N/A' },
20
+ 'NVP (pediatric) bottles' => { drugs: [21, 817, 968, 971], quantity: 'N/A' },
21
+ 'Other (adult) bottles' => { drugs: [
22
+ 3, 5, 6, 10, 38, 39, 40, 42, 89, 614, 730, 731, 734, 738,
23
+ 814, 815, 932, 933, 934, 952, 954, 955, 957, 969, 976, 978, 984, 1217, 1213, 14
24
+ ], quantity: 'N/A' },
25
+ 'Other (pediatric) bottles' => { drugs: [
26
+ 2, 9, 28, 29, 30, 31, 32, 36, 37, 41, 70, 71, 72, 90, 91,
27
+ 95, 104, 177, 732, 733, 736, 737, 813, 816, 981, 1043, 1044, 1214, 1215
28
+ ], quantity: 'N/A' }
29
+ }.freeze
30
+
31
+ def initialize(start_date:, end_date:, **_kwargs)
32
+ @start_date = start_date
33
+ @end_date = end_date
34
+ @location = _kwargs[:location]
35
+ end
36
+
37
+ def find_report
38
+ initialize_report
39
+ process_report
40
+ # remove the drug_id from the report
41
+ @report.each { |category| category.delete(:drug_id) }
42
+ @report
43
+ end
44
+
45
+ private
46
+
47
+ def initialize_report
48
+ @report = []
49
+ DRUG_CATEGORY.each do |category, drug|
50
+ @report << {
51
+ category:,
52
+ drug_id: drug[:drugs],
53
+ units: 0,
54
+ quantity: drug[:quantity],
55
+ granular_spec: []
56
+ }
57
+ end
58
+ end
59
+
60
+ # rubocop:disable Metrics/AbcSize
61
+ # rubocop:disable Metrics/CyclomaticComplexity
62
+ def process_report
63
+ current_stock.each do |item|
64
+ # Find the drug category
65
+ drug_category = @report.find do |category|
66
+ category[:drug_id].include?(item.drug_id) && category[:quantity] == item.pack_size
67
+ end
68
+ drug_category ||= @report.find do |category|
69
+ category[:drug_id].include?(item.drug_id) && category[:quantity] == 'N/A'
70
+ end
71
+ next unless drug_category
72
+
73
+ bottles = (item.current_quantity / item.pack_size).to_i
74
+ drug_category[:units] += bottles
75
+ # check if the drug is already in the granular_spec
76
+ granular_spec = drug_category[:granular_spec].find { |spec| spec[:drug_name] == item.drug.name }
77
+ if granular_spec
78
+ granular_spec[:units] += bottles
79
+ else
80
+ drug_category[:granular_spec] << {
81
+ drug_name: item.drug.name,
82
+ units: bottles
83
+ }
84
+ end
85
+ end
86
+ end
87
+ # rubocop:enable Metrics/AbcSize
88
+ # rubocop:enable Metrics/CyclomaticComplexity
89
+
90
+ def current_stock
91
+ drugs = DRUG_CATEGORY.map { |_, drug| drug[:drugs] }.flatten.uniq
92
+ ::PharmacyBatchItem.where(
93
+ "expiry_date >= ? AND delivery_date <= ? AND drug_id IN (?) AND current_quantity > 0 #{!@location.blank? ? "AND site_id = #{@location}" : ''}", @end_date, @end_date, drugs
94
+ )
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MalawiHivProgramReports
4
+ module Pepfar
5
+ class TbPrev
6
+
7
+ include MalawiHivProgramReports::Adapters::Moh::Custom
8
+ attr_accessor :start_date, :end_date, :location
9
+
10
+ def initialize(start_date:, end_date:, **kwargs)
11
+ @completion_start_date = start_date&.to_date.strftime('%Y-%m-%d 00:00:00')
12
+ @completion_end_date = end_date&.to_date.strftime('%Y-%m-%d 23:59:59')
13
+ @location = kwargs[:location]
14
+ end
15
+
16
+ def find_report
17
+ data
18
+ end
19
+
20
+ private
21
+
22
+ def data
23
+ meds = tb_med_dispensations
24
+ return {} if meds.blank?
25
+
26
+ patient_ids = meds.map { |m| m['patient_id'].to_i }.uniq
27
+ hiv_program_clients = on_art_in_reporting_period(patient_ids)
28
+ return if hiv_program_clients.blank?
29
+
30
+ clients_in_hiv_program = {}
31
+
32
+ hiv_program_clients.each do |e|
33
+ clients_in_hiv_program[e['patient_id'].to_i] = e
34
+ end
35
+
36
+ clients = {}
37
+ initiation_start_date = (@completion_start_date&.to_date - 6.month)&.to_date
38
+
39
+ meds.each do |m|
40
+ patient_id = m['patient_id'].to_i
41
+ inh_start_date = min_inh_start_date(patient_id)
42
+ next unless inh_start_date&.to_date >= initiation_start_date&.to_date &&
43
+ inh_start_date&.to_date < @completion_start_date&.to_date
44
+
45
+ p = clients_in_hiv_program[patient_id]
46
+ next if p.blank?
47
+
48
+ earliest_start_date = begin
49
+ p['earliest_start_date']&.to_date
50
+ rescue StandardError
51
+ p['date_enrolled']&.to_date
52
+ end
53
+
54
+ quantity = m['quantity'].to_f
55
+ if clients[patient_id].blank?
56
+ clients[patient_id] = {
57
+ date_enrolled: p['date_enrolled']&.to_date,
58
+ earliest_start_date:,
59
+ gender: begin
60
+ p['gender'].upcase.first
61
+ rescue StandardError
62
+ 'Unknown'
63
+ end,
64
+ birthdate: begin
65
+ p['birthdate']&.to_date
66
+ rescue StandardError
67
+ 'Unknow'
68
+ end,
69
+ age_group: p['age_group'],
70
+ course_completed: false,
71
+ client: new_client(earliest_start_date, inh_start_date),
72
+ quantity: 0
73
+ }
74
+ end
75
+
76
+ clients[patient_id][:quantity] += quantity
77
+
78
+ clients[patient_id][:course_completed] = true if clients[patient_id][:quantity] >= 168
79
+ end
80
+
81
+ clients
82
+ end
83
+
84
+ def min_inh_start_date(patient_id)
85
+ start_date = ActiveRecord::Base.connection.select_one <<-SQL
86
+ SELECT
87
+ DATE(MIN(o.start_date)) date
88
+ FROM person p
89
+ INNER JOIN orders o ON o.patient_id = p.person_id
90
+ INNER JOIN drug_order t ON o.order_id = t.order_id
91
+ INNER JOIN drug d ON d.drug_id = t.drug_inventory_id
92
+ INNER JOIN encounter e ON e.patient_id = o.patient_id AND e.program_id = 1
93
+ WHERE o.voided = 0 AND o.patient_id = #{patient_id}
94
+ AND d.concept_id = 656 AND t.quantity > 0;
95
+ SQL
96
+
97
+ start_date['date']&.to_date
98
+ end
99
+
100
+ def new_client(earliest_start_date, min_date)
101
+ return 'Previously on ART' if earliest_start_date.blank?
102
+
103
+ med_start_date = min_date&.to_date
104
+ med_end_date = (earliest_start_date&.to_date + 90.day)&.to_date
105
+
106
+ return 'New on ART' if med_start_date >= earliest_start_date&.to_date && med_start_date < med_end_date
107
+
108
+ 'Previously on ART'
109
+ end
110
+
111
+ def client_outcome(patient_id)
112
+ outcome_string = ActiveRecord::Base.connection.select_one <<-SQL
113
+ SELECT pepfar_patient_outcome(#{patient_id}, DATE('#{@end_date}')) outcome;
114
+ SQL
115
+
116
+ outcome_string['outcome']
117
+ end
118
+
119
+ def on_art_in_reporting_period(patient_ids)
120
+ ActiveRecord::Base.connection.select_all <<-SQL
121
+ select
122
+ p.patient_id AS patient_id, pe.birthdate, pe.gender,
123
+ patient_date_enrolled(CAST(p.patient_id AS INT), CAST(#{@location} AS INT)) AS date_enrolled,
124
+ date_antiretrovirals_started(CAST(p.patient_id AS INT), CAST(MIN(s.start_date) AS DATE), CAST(#{@location} AS INT)) AS earliest_start_date,
125
+ disaggregated_age_group(CAST(pe.birthdate AS DATE), DATE('#{@completion_end_date}')) age_group
126
+ from
127
+ patient_program p
128
+ left join person pe ON pe.person_id = p.patient_id
129
+ left join patient_state s ON p.patient_program_id = s.patient_program_id
130
+ left join person ON person.person_id = p.patient_id
131
+ where
132
+ p.voided = 0
133
+ and s.voided = 0
134
+ and p.program_id = 1
135
+ and s.state = 7
136
+ and #{in_manager(column: 'p.patient_id', values: patient_ids)}
137
+ group by p.patient_id, pe.birthdate, pe.gender, p.date_enrolled
138
+ HAVING date_enrolled IS NOT NULL;
139
+ SQL
140
+ end
141
+
142
+ def tb_med_dispensations
143
+ initiation_start_date = (@completion_start_date&.to_date - 6.month).strftime('%Y-%m-%d 00:00:00')
144
+
145
+ ActiveRecord::Base.connection.select_all <<-SQL
146
+ SELECT
147
+ o.patient_id, t.drug_inventory_id, t.quantity,
148
+ o.start_date, o.auto_expire_date
149
+ FROM person p
150
+ INNER JOIN orders o ON o.patient_id = p.person_id
151
+ INNER JOIN drug_order t ON o.order_id = t.order_id
152
+ INNER JOIN drug d ON d.drug_id = t.drug_inventory_id
153
+ INNER JOIN encounter e ON e.patient_id = o.patient_id AND e.program_id = 1
154
+ WHERE o.voided = 0 AND (o.start_date
155
+ BETWEEN '#{initiation_start_date}' AND '#{@completion_end_date}')
156
+ AND d.concept_id = 656 AND t.quantity > 0
157
+ GROUP BY o.patient_id, t.drug_inventory_id, DATE(o.start_date), t.quantity, o.start_date, o.auto_expire_date
158
+ ORDER BY o.patient_id;
159
+ SQL
160
+ end
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MalawiHivProgramReports
4
+ module Pepfar
5
+ ##
6
+ # Patients who started TPT just before the start of the current
7
+ # and have finished within the current reporting period.
8
+ class TbPrev2
9
+ attr_reader :start_date, :end_date
10
+
11
+ include Utils
12
+
13
+ def initialize(start_date:, end_date:, **_kwargs)
14
+ @start_date = ActiveRecord::Base.connection.quote(start_date)
15
+ @end_date = ActiveRecord::Base.connection.quote(end_date)
16
+ end
17
+
18
+ def find_report
19
+ report = init_report
20
+ patients = group_patients_by_tpt_course(patients_on_tpt)
21
+
22
+ load_patients_into_report(report, patients.six_h, '6H') do |patient|
23
+ # 6H has a constant dosage of 1 pill per day
24
+ patient['total_pills_taken'].to_i >= FULL_6H_COURSE_PILLS
25
+ end
26
+
27
+ load_patients_into_report(report, patients.three_hp, '3HP') do |patient|
28
+ # 3HP daily dosages vary by patient weight can't use easily use pills
29
+ # to determine course completion
30
+ patient['total_days_on_medication'].days >= FULL_3HP_COURSE_DAYS
31
+ end
32
+
33
+ report
34
+ end
35
+
36
+ private
37
+
38
+ FULL_6H_COURSE_PILLS = 168
39
+ FULL_3HP_COURSE_DAYS = (28.days - 1.day) + (56.days - 1.day) # 82 Days
40
+ # NOTE: Arrived at 82 days above from how 3HP is prescribed. 1st time prescription
41
+ # is 1 month (28 days) then 2 months (56 days). And we assume that patients
42
+ # start taking medication the very same they are given thus we subtract 1.
43
+
44
+ def init_report
45
+ pepfar_age_groups.each_with_object({}) do |age_group, report|
46
+ report[age_group] = %w[M F Unknown].each_with_object({}) do |gender, gender_sub_report|
47
+ gender_sub_report[gender] = %w[6H 3HP].each_with_object({}) do |tpt, tpt_sub_report|
48
+ tpt_sub_report[tpt] = {
49
+ started_new_on_art: [],
50
+ started_previously_on_art: [],
51
+ completed_new_on_art: [],
52
+ completed_previously_on_art: []
53
+ }
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ def load_patients_into_report(report, patients, tpt, &patient_has_completed_tpt)
60
+ patients.each do |patient|
61
+ age_group = patient['age_group']
62
+ gender = patient['gender']&.first&.upcase || 'Unknown'
63
+ tpt_states = find_patient_tpt_state(patient, &patient_has_completed_tpt)
64
+
65
+ tpt_states.each do |tpt_state|
66
+ report[age_group][gender][tpt][tpt_state] << patient
67
+ end
68
+ end
69
+ end
70
+
71
+ def find_patient_tpt_state(patient, &patient_has_completed_tpt)
72
+ if patient_has_completed_tpt[patient]
73
+ return %i[started_new_on_art completed_new_on_art] if patient_new_on_art?(patient)
74
+
75
+ return %i[started_previously_on_art completed_previously_on_art]
76
+ end
77
+
78
+ return %i[started_new_on_art] if patient_new_on_art?(patient)
79
+
80
+ %i[started_previously_on_art]
81
+ end
82
+
83
+ def patient_new_on_art?(patient)
84
+ tpt_initiation_date = patient['tpt_initiation_date'].to_date
85
+ art_start_date = patient['art_start_date'].to_date
86
+
87
+ (tpt_initiation_date >= art_start_date) && (tpt_initiation_date < art_start_date + 90.days)
88
+ end
89
+
90
+ def patients_on_tpt
91
+ ActiveRecord::Base.connection.select_all <<~SQL
92
+ SELECT person.person_id AS patient_id,
93
+ patient_identifier.identifier AS arv_number,
94
+ DATE(MIN(orders.start_date)) AS tpt_initiation_date,
95
+ date_antiretrovirals_started(person.person_id, MIN(patient_state.start_date)) AS art_start_date,
96
+ SUM(drug_order.quantity) AS total_pills_taken,
97
+ SUM(DATEDIFF(orders.auto_expire_date, orders.start_date)) AS total_days_on_medication,
98
+ person.gender,
99
+ person.birthdate,
100
+ disaggregated_age_group(person.birthdate, DATE(#{end_date})) AS age_group,
101
+ GROUP_CONCAT(DISTINCT orders.concept_id SEPARATOR ',') AS drug_concepts
102
+ FROM person
103
+ LEFT JOIN patient_identifier
104
+ ON patient_identifier.patient_id = person.person_id
105
+ AND patient_identifier.voided = 0
106
+ AND patient_identifier.identifier_type IN (SELECT patient_identifier_type_id FROM patient_identifier_type WHERE name = 'ARV Number')
107
+ INNER JOIN patient_program
108
+ ON patient_program.patient_id = person.person_id
109
+ AND patient_program.program_id IN (SELECT program_id FROM program WHERE name = 'HIV PROGRAM')
110
+ AND patient_program.voided = 0
111
+ INNER JOIN patient_state
112
+ ON patient_state.patient_program_id = patient_program.patient_program_id
113
+ AND patient_state.state = 7 /* State: 7 == On antiretrovirals */
114
+ AND patient_state.start_date < DATE(#{start_date})
115
+ AND patient_state.voided = 0
116
+ INNER JOIN encounter AS prescription_encounter
117
+ ON prescription_encounter.patient_id = patient_program.patient_id
118
+ AND prescription_encounter.program_id IN (SELECT program_id FROM program WHERE name = 'HIV PROGRAM')
119
+ AND prescription_encounter.encounter_type IN (SELECT encounter_type_id FROM encounter_type WHERE name = 'Treatment')
120
+ AND prescription_encounter.encounter_datetime >= DATE(#{start_date}) - INTERVAL 6 MONTH
121
+ AND prescription_encounter.encounter_datetime < DATE(#{start_date})
122
+ AND prescription_encounter.voided = 0
123
+ INNER JOIN orders
124
+ ON orders.encounter_id = prescription_encounter.encounter_id
125
+ AND orders.order_type_id IN (SELECT order_type_id FROM order_type WHERE name = 'Drug order')
126
+ AND orders.start_date >= DATE(#{start_date}) - INTERVAL 6 MONTH
127
+ AND orders.start_date < DATE(#{start_date})
128
+ AND orders.voided = 0
129
+ INNER JOIN concept_name
130
+ ON concept_name.concept_id = orders.concept_id
131
+ AND concept_name.name IN ('Rifapentine', 'Isoniazid', 'Isoniazid/Rifapentine')
132
+ INNER JOIN drug_order
133
+ ON drug_order.order_id = orders.order_id
134
+ AND drug_order.quantity > 0
135
+ WHERE person.voided = 0
136
+ AND person.person_id NOT IN (
137
+ /* People who had a dispensation prior to the 3 to 9 months before start of reporting period.
138
+ Continuing medication after a 9 months break is considered a restart hence such patients
139
+ are classified as new on TPT.
140
+ */
141
+ SELECT DISTINCT encounter.patient_id
142
+ FROM encounter
143
+ INNER JOIN orders
144
+ ON orders.encounter_id = encounter.encounter_id
145
+ AND orders.concept_id IN (SELECT concept_id FROM concept_name WHERE name IN ('Rifapentine', 'Isoniazid') AND voided = 0)
146
+ AND orders.order_type_id IN (SELECT order_type_id FROM order_type WHERE name = 'Drug order')
147
+ AND orders.start_date < DATE(#{start_date}) - INTERVAL 6 MONTH
148
+ AND orders.start_date >= DATE(#{start_date}) - INTERVAL 15 MONTH
149
+ AND orders.voided = 0
150
+ INNER JOIN drug_order
151
+ ON drug_order.order_id = orders.order_id
152
+ AND drug_order.quantity > 0
153
+ WHERE encounter.program_id IN (SELECT program_id FROM program WHERE name = 'HIV PROGRAM')
154
+ AND encounter.encounter_type IN (SELECT encounter_type_id FROM encounter_type WHERE name = 'Treatment')
155
+ AND encounter.encounter_datetime < DATE(#{start_date}) - INTERVAL 6 MONTH
156
+ AND encounter.encounter_datetime >= DATE(#{start_date}) - INTERVAL 15 MONTH
157
+ AND encounter.voided = 0
158
+ ) AND person.person_id NOT IN (
159
+ /* External consultations */
160
+ SELECT DISTINCT registration_encounter.patient_id
161
+ FROM patient_program
162
+ INNER JOIN program ON program.name = 'HIV PROGRAM'
163
+ INNER JOIN encounter AS registration_encounter
164
+ ON registration_encounter.patient_id = patient_program.patient_id
165
+ AND registration_encounter.program_id = patient_program.program_id
166
+ AND registration_encounter.encounter_datetime < DATE(#{end_date}) + INTERVAL 1 DAY
167
+ AND registration_encounter.voided = 0
168
+ INNER JOIN (
169
+ SELECT MAX(encounter.encounter_datetime) AS encounter_datetime, encounter.patient_id
170
+ FROM encounter
171
+ INNER JOIN encounter_type
172
+ ON encounter_type.encounter_type_id = encounter.encounter_type
173
+ AND encounter_type.name = 'Registration'
174
+ INNER JOIN program
175
+ ON program.program_id = encounter.program_id
176
+ AND program.name = 'HIV PROGRAM'
177
+ WHERE encounter.encounter_datetime < DATE(#{end_date}) AND encounter.voided = 0
178
+ GROUP BY encounter.patient_id
179
+ ) AS max_registration_encounter
180
+ ON max_registration_encounter.patient_id = registration_encounter.patient_id
181
+ AND max_registration_encounter.encounter_datetime = registration_encounter.encounter_datetime
182
+ INNER JOIN obs AS patient_type_obs
183
+ ON patient_type_obs.encounter_id = registration_encounter.encounter_id
184
+ AND patient_type_obs.concept_id IN (SELECT concept_id FROM concept_name WHERE name = 'Type of patient' AND voided = 0)
185
+ AND patient_type_obs.value_coded IN (SELECT concept_id FROM concept_name WHERE name IN ('Drug refill', 'External consultation') AND voided = 0)
186
+ AND patient_type_obs.voided = 0
187
+ WHERE patient_program.voided = 0
188
+ )
189
+ GROUP BY person.person_id
190
+ SQL
191
+ end
192
+
193
+ ##
194
+ # Groups patients into their TPT categories (ie 6H and 3HP) based on their drugs
195
+ #
196
+ # Returns an object with a three_hp and six_h methods, each of which
197
+ # is an array of patients for that category.
198
+ def group_patients_by_tpt_course(patients)
199
+ patients.each_with_object(OpenStruct.new(six_h: [], three_hp: [])) do |patient, categories|
200
+ if patient_on_3hp?(patient)
201
+ categories.three_hp << patient
202
+ else
203
+ categories.six_h << patient
204
+ end
205
+ end
206
+ end
207
+
208
+ def patient_on_3hp?(patient)
209
+ drug_concepts = patient['drug_concepts'].split(',').collect(&:to_i)
210
+ drug_concepts.intersect?([rifapentine_concept.concept_id, isoniazid_rifapentine_concept&.concept_id])
211
+ end
212
+
213
+ def rifapentine_concept
214
+ @rifapentine_concept ||= ::ConceptName.find_by!(name: 'Rifapentine')
215
+ end
216
+
217
+ def isoniazid_rifapentine_concept
218
+ @isoniazid_rifapentine_concept ||= ::ConceptName.find_by!(name: 'Isoniazid/Rifapentine')
219
+ end
220
+ end
221
+ end
222
+ end