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,235 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MalawiHivProgramReports
4
+ module Moh
5
+ ##
6
+ # Patients newly initiated on TPT disaggregated by regimen type and age group.
7
+ #
8
+ # Newly initiated is defined as patients who have received TPT for the first
9
+ # time in the reporting period or patients who have gone on a TPT course
10
+ # break for a period that is at least 9 months long before restarting TPT
11
+ # in the current reporting period.
12
+ # rubocop:disable Metrics/ClassLength
13
+ class TptNewlyInitiated
14
+ attr_reader :start_date, :end_date
15
+
16
+ def initialize(start_date:, end_date:, **kwargs)
17
+ @start_date = ActiveRecord::Base.connection.quote(start_date)
18
+ @end_date = ActiveRecord::Base.connection.quote(end_date)
19
+ @occupation = kwargs[:occupation]
20
+ @location = kwargs[:location]
21
+ @adapter = ActiveRecord::Base.connection.adapter_name.downcase
22
+ end
23
+
24
+ def find_report
25
+ report = init_report
26
+ newly_initiated_on_tpt.each do |tpt, patients|
27
+ patients.each do |patient|
28
+ patient_id = patient['patient_id']
29
+ person = ActiveRecord::Base.connection.select_one <<~SQL
30
+ SELECT disaggregated_age_group(birthdate, DATE('#{end_date.to_date}')) AS age_group,
31
+ patient_identifier.identifier AS arv_number, person.*
32
+ FROM person
33
+ LEFT JOIN patient_identifier ON patient_identifier.patient_id = person.person_id
34
+ AND patient_identifier.identifier_type IN (SELECT patient_identifier_type_id FROM patient_identifier_type
35
+ WHERE name = 'ARV Number') AND patient_identifier.voided = 0
36
+ WHERE person_id = #{patient_id} LIMIT 1;
37
+ SQL
38
+ age_group = person['age_group']
39
+ gender = person['gender']&.strip&.first&.upcase || 'Unknown'
40
+ # course = patient_on_3hp?(patient) ? '3HP' : '6H'
41
+
42
+ report[age_group][tpt][gender] << {
43
+ patient_id: person['person_id'],
44
+ birthdate: person['birthdate'],
45
+ arv_number: person['arv_number'],
46
+ gender:,
47
+ dispensation_date: dispensation_date(patient_id, patient['drug_concepts']),
48
+ art_start_date: patient['earliest_start_date'],
49
+ tpt_start_date: patient['tpt_start_date']
50
+ }
51
+ end
52
+ end
53
+ report['Location'] = Location.current.city_village
54
+ report
55
+ end
56
+
57
+ private
58
+
59
+ AGE_GROUPS = [
60
+ 'Unknown',
61
+ '<1 year',
62
+ '1-4 years', '5-9 years',
63
+ '10-14 years', '15-19 years',
64
+ '20-24 years',
65
+ '25-29 years', '30-34 years',
66
+ '35-39 years', '40-44 years',
67
+ '45-49 years', '50-54 years',
68
+ '55-59 years', '60-64 years',
69
+ '65-69 years', '70-74 years',
70
+ '75-79 years', '80-84 years',
71
+ '85-89 years',
72
+ '90 plus years'
73
+ ].freeze
74
+
75
+ def dispensation_date(patient_id, concept_ids)
76
+ order = ActiveRecord::Base.connection.select_one <<~SQL
77
+ SELECT MIN(orders.start_date) start_date FROM orders
78
+ INNER JOIN drug_order
79
+ ON drug_order.order_id = orders.order_id
80
+ AND drug_order.quantity > 0 /* This implies that a dispensation was made */
81
+ INNER JOIN drug
82
+ ON drug.drug_id = drug_order.drug_inventory_id
83
+ AND drug.concept_id IN(#{concept_ids})
84
+ INNER JOIN encounter ON encounter.encounter_id = orders.encounter_id
85
+ AND encounter.program_id = 1
86
+ WHERE DATE(orders.start_date) BETWEEN '#{start_date.to_date}' AND '#{end_date.to_date}'
87
+ AND orders.voided = 0 AND orders.patient_id = #{patient_id};
88
+ SQL
89
+
90
+ order['start_date'].to_date
91
+ end
92
+
93
+ def init_report
94
+ AGE_GROUPS.each_with_object({}) do |age_group, report|
95
+ report[age_group] = {
96
+ '3HP_new' => { 'M' => [], 'F' => [], 'Unknown' => [] },
97
+ '6H_new' => { 'M' => [], 'F' => [], 'Unknown' => [] },
98
+ '3HP_prev' => { 'M' => [], 'F' => [], 'Unknown' => [] },
99
+ '6H_prev' => { 'M' => [], 'F' => [], 'Unknown' => [] }
100
+ }
101
+ end
102
+ end
103
+
104
+ def patient_on_3hp?(patient)
105
+ patient['drug_concepts'].split(',').collect(&:to_i).include?(rifapentine_concept.concept_id)
106
+ end
107
+
108
+ def rifapentine_concept
109
+ @rifapentine_concept ||= ::ConceptName.find_by!(name: 'Rifapentine')
110
+ end
111
+
112
+ def newly_initiated_on_tpt
113
+ tpt = Cohort::Tpt.new(start_date: start_date.to_date, end_date: end_date.to_date, occupation: @occupation,
114
+ location: @location)
115
+ data = {}
116
+ three_hp = tpt.newly_initiated_on_3hp
117
+ ipt = tpt.newly_initiated_on_ipt
118
+ data['3HP_new'] = three_hp.select do |record|
119
+ record['earliest_start_date'].to_date > end_date.to_date - 3.month
120
+ end
121
+ data['6H_new'] = ipt.select { |record| record['earliest_start_date'].to_date > end_date.to_date - 3.month }
122
+ data['3HP_prev'] = three_hp.select do |record|
123
+ record['earliest_start_date'].to_date <= end_date.to_date - 3.month
124
+ end
125
+ data['6H_prev'] = ipt.select { |record| record['earliest_start_date'].to_date <= end_date.to_date - 3.month }
126
+ data
127
+ end
128
+
129
+ def newly_initiated_on_tpt_old
130
+ ActiveRecord::Base.connection.select_all <<~SQL
131
+ SELECT patient_program.patient_id,
132
+ disaggregated_age_group(person.birthdate, DATE(#{end_date})) AS age_group,
133
+ DATE(prescription_encounter.encounter_datetime) AS prescription_date,
134
+ person_name.given_name,
135
+ person_name.family_name,
136
+ person.birthdate,
137
+ person.gender,
138
+ patient_identifier.identifier AS arv_number,
139
+ GROUP_CONCAT(DISTINCT orders.concept_id SEPARATOR ',') AS drug_concepts
140
+ FROM person
141
+ LEFT JOIN person_name
142
+ ON person_name.person_id = person.person_id
143
+ AND person_name.voided = 0
144
+ LEFT JOIN patient_identifier
145
+ ON patient_identifier.patient_id = person.person_id
146
+ AND patient_identifier.identifier_type IN (SELECT patient_identifier_type_id FROM patient_identifier_type WHERE name = 'ARV Number')
147
+ AND patient_identifier.voided = 0
148
+ INNER JOIN patient_program
149
+ ON patient_program.patient_id = person.person_id
150
+ AND patient_program.program_id IN (SELECT program_id FROM program WHERE name = 'HIV PROGRAM')
151
+ INNER JOIN encounter AS prescription_encounter
152
+ ON prescription_encounter.patient_id = person.person_id
153
+ AND prescription_encounter.encounter_type IN (SELECT encounter_type_id FROM encounter_type WHERE name = 'Treatment')
154
+ AND prescription_encounter.program_id IN (SELECT program_id FROM program WHERE name = 'HIV PROGRAM')
155
+ AND prescription_encounter.encounter_datetime >= #{start_date}
156
+ AND prescription_encounter.encounter_datetime < DATE(#{end_date}) + INTERVAL 1 DAY
157
+ AND prescription_encounter.voided = 0
158
+ INNER JOIN orders AS orders
159
+ ON orders.encounter_id = prescription_encounter.encounter_id
160
+ AND orders.order_type_id = (SELECT order_type_id FROM order_type WHERE name = 'Drug order' LIMIT 1)
161
+ AND orders.start_date >= #{start_date}
162
+ AND orders.start_date < DATE(#{end_date}) + INTERVAL 1 DAY
163
+ AND orders.voided = 0
164
+ INNER JOIN drug_order
165
+ ON drug_order.order_id = orders.order_id
166
+ AND drug_order.quantity > 0 /* This implies that a dispensation was made */
167
+ INNER JOIN drug
168
+ ON drug.drug_id = drug_order.drug_inventory_id
169
+ INNER JOIN concept_name AS tpt_drug_concepts
170
+ ON tpt_drug_concepts.concept_id = drug.concept_id
171
+ AND tpt_drug_concepts.name IN ('Rifapentine', 'Isoniazid')
172
+ AND tpt_drug_concepts.voided = 0
173
+ WHERE patient_program.patient_id NOT IN (
174
+ /* Filter out patients who received TPT before current reporting period */
175
+ SELECT DISTINCT patient_program.patient_id
176
+ FROM patient_program
177
+ INNER JOIN encounter AS prescription_encounter
178
+ ON prescription_encounter.patient_id = patient_program.patient_id
179
+ AND prescription_encounter.encounter_type IN (SELECT encounter_type_id FROM encounter_type WHERE name = 'Treatment')
180
+ AND prescription_encounter.program_id = (SELECT program_id FROM program WHERE name = 'HIV PROGRAM' LIMIT 1)
181
+ AND prescription_encounter.voided = 0
182
+ INNER JOIN orders
183
+ ON orders.encounter_id = prescription_encounter.encounter_id
184
+ AND orders.order_type_id = (SELECT order_type_id FROM order_type WHERE name = 'Drug order' LIMIT 1)
185
+ AND orders.start_date < #{start_date}
186
+ /* Re-initiates defined as those who stopped TPT for a period of at least 9 months
187
+ and have restarted TPT are also included in the report */
188
+ AND orders.start_date >= DATE(#{start_date}) - INTERVAL 9 MONTH
189
+ AND orders.voided = 0
190
+ INNER JOIN concept_name AS tpt_drug_concepts
191
+ ON tpt_drug_concepts.concept_id = orders.concept_id
192
+ AND tpt_drug_concepts.name IN ('Rifapentine', 'Isoniazid')
193
+ AND tpt_drug_concepts.voided = 0
194
+ INNER JOIN drug_order AS drug_order
195
+ ON drug_order.order_id = orders.order_id
196
+ AND drug_order.quantity > 0
197
+ WHERE patient_program.program_id IN (SELECT program_id FROM program WHERE name = 'HIV PROGRAM')
198
+ ) AND patient_program.patient_id NOT IN (
199
+ /* External consultations */
200
+ SELECT DISTINCT registration_encounter.patient_id
201
+ FROM patient_program
202
+ INNER JOIN program ON program.name = 'HIV PROGRAM'
203
+ INNER JOIN encounter AS registration_encounter
204
+ ON registration_encounter.patient_id = patient_program.patient_id
205
+ AND registration_encounter.program_id = patient_program.program_id
206
+ AND registration_encounter.encounter_datetime < DATE(#{end_date}) + INTERVAL 1 DAY
207
+ AND registration_encounter.voided = 0
208
+ INNER JOIN (
209
+ SELECT MAX(encounter.encounter_datetime) AS encounter_datetime, encounter.patient_id
210
+ FROM encounter
211
+ INNER JOIN encounter_type
212
+ ON encounter_type.encounter_type_id = encounter.encounter_type
213
+ AND encounter_type.name = 'Registration'
214
+ INNER JOIN program
215
+ ON program.program_id = encounter.program_id
216
+ AND program.name = 'HIV PROGRAM'
217
+ WHERE encounter.encounter_datetime < DATE(#{end_date}) AND encounter.voided = 0
218
+ GROUP BY encounter.patient_id
219
+ ) AS max_registration_encounter
220
+ ON max_registration_encounter.patient_id = registration_encounter.patient_id
221
+ AND max_registration_encounter.encounter_datetime = registration_encounter.encounter_datetime
222
+ INNER JOIN obs AS patient_type_obs
223
+ ON patient_type_obs.encounter_id = registration_encounter.encounter_id
224
+ AND patient_type_obs.concept_id IN (SELECT concept_id FROM concept_name WHERE name = 'Type of patient' AND voided = 0)
225
+ AND patient_type_obs.value_coded IN (SELECT concept_id FROM concept_name WHERE name IN ('Drug refill', 'External consultation') AND voided = 0)
226
+ AND patient_type_obs.voided = 0
227
+ WHERE patient_program.voided = 0
228
+ )
229
+ GROUP BY patient_program.patient_id
230
+ SQL
231
+ end
232
+ end
233
+ # rubocop:enable Metrics/ClassLength
234
+ end
235
+ end
@@ -0,0 +1,25 @@
1
+ module MalawiHivProgramReports
2
+ module Pepfar
3
+ class DefaulterList
4
+
5
+ attr_reader :start_date, :end_date, :location
6
+
7
+ def initialize(start_date:, end_date:, **kwargs)
8
+ @start_date = start_date
9
+ @end_date = end_date
10
+ @location = kwargs[:location]
11
+ end
12
+
13
+ def find_report
14
+ MalawiHivProgramReports::Moh::Cohort.new(
15
+ type: 'defaulter_list',
16
+ location: @location,
17
+ name: 'defaulter_list',
18
+ start_date: start_date,
19
+ end_date: end_date
20
+ ).defaulter_list('pepfar')
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ module MalawiHivProgramReports
2
+ module Pepfar
3
+ # rubocop:disable Metrics/ClassLength
4
+ class MaternalStatus
5
+ include Utils
6
+ include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
7
+ include MalawiHivProgramReports::Adapters::Moh::Custom
8
+ include MalawiHivProgramReports::Utils::ModelUtils
9
+ attr_reader :start_date, :end_date, :location
10
+
11
+ def initialize(start_date:, end_date:, **kwargs)
12
+ @start_date = start_date&.to_date
13
+ raise InvalidParameterError, 'start_date is required' unless @start_date
14
+
15
+ @end_date = end_date&.to_date || @start_date + 12.months
16
+ raise InvalidParameterError, "start_date can't be greater than end_date" if @start_date > @end_date
17
+
18
+ @occupation = kwargs.delete(:occupation)
19
+ @type = kwargs.delete(:application)
20
+ @location = kwargs.delete(:location)
21
+ @patient_ids = kwargs.delete(:patient_ids)&.split(',') || []
22
+ end
23
+
24
+ def find_report
25
+ MalawiHivProgramReports::Pepfar::ViralLoadCoverage2.new(tx_curr_definition: 'pepfar', start_date: @start_date, end_date: @end_date, location: @location).vl_maternal_status(@patient_ids)
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MalawiHivProgramReports
4
+ module Pepfar
5
+ # this module returns all the patient records on when
6
+ # when the patient started ART
7
+ # plus the last viral load result
8
+ class PatientStartVl
9
+
10
+ attr_reader :start_date, :end_date, :location, :patient_ids
11
+
12
+ include Utils
13
+ include MalawiHivProgramReports::Adapters::Moh::Custom
14
+
15
+ def initialize(start_date:, end_date:, **kwargs)
16
+ @start_date = start_date
17
+ @end_date = end_date
18
+ @location = kwargs[:location]
19
+ @patient_ids = kwargs[:patient_ids]
20
+ raise InvalidParameterError, 'Patient IDs are required' if patient_ids.blank?
21
+ end
22
+
23
+ def find_report
24
+ get_patients_last_vl_and_latest_result
25
+ end
26
+
27
+ def get_patients_last_vl_and_latest_result
28
+ ids = patient_ids.split(',')
29
+ ActiveRecord::Base.connection.select_all <<~SQL
30
+ SELECT p.person_id AS patient_id,
31
+ patient_start_date(p.person_id) AS art_start_date,
32
+ p.birthdate AS birthdate,
33
+ p.gender,
34
+ MIN(pi.identifier)
35
+ FROM person p
36
+ LEFT JOIN patient_identifier pi ON pi.patient_id = p.person_id AND pi.voided = 0 AND pi.identifier_type = 4
37
+ WHERE p.voided = 0
38
+ AND #{in_manager(column: 'p.person_id', values: ids)}
39
+ #{site_manager(operator: 'AND', column: 'p.site_id', location: location)}
40
+ GROUP BY p.person_id, p.birthdate, p.gender
41
+ SQL
42
+ end
43
+ end
44
+ end
45
+ end