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