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