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,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MalawiHivProgramReports
4
+ module Clinic
5
+ # Generates a hypertension report for a clinic
6
+ # rubocop:disable Metrics/ClassLength
7
+ class HypertensionReport
8
+ AGE_GROUPS = [
9
+ '20-24 years',
10
+ '25-29 years', '30-34 years',
11
+ '35-39 years', '40-44 years',
12
+ '45-49 years', '50-54 years',
13
+ '55-59 years', '60-64 years',
14
+ '65-69 years', '70-74 years',
15
+ '75-79 years', '80-84 years',
16
+ '85-89 years',
17
+ '90 plus years'
18
+ ].freeze
19
+
20
+ GENDER = %w[M F].freeze
21
+
22
+ DRUG_MAPPING = {
23
+ 'Hydrochlorothiazide (25mg tablet)' => :hydrochlorothiazide_25mg,
24
+ 'HCZ (25mg tablet)' => :hydrochlorothiazide_25mg,
25
+ 'Hctz (25mg)' => :hydrochlorothiazide_25mg,
26
+ 'Amlodipine (5mg tablet)' => :amlodipine_5mg,
27
+ 'Amlodipine 5mg' => :amlodipine_5mg,
28
+ 'Amlodipine (5mg)' => :amlodipine_5mg,
29
+ 'Amlodipine (10mg tablet)' => :amlodipine_10mg,
30
+ 'Enalapril (5mg tablet)' => :enalapril_5mg,
31
+ 'Enalapril (5mg)' => :enalapril_5mg,
32
+ 'Enalapril (10mg tablet)' => :enalapril_10mg,
33
+ 'Enalapril (10mg)' => :enalapril_10mg,
34
+ 'Atenolol (50mg tablet)' => :atenolol_50mg,
35
+ 'Atenolol (50mg)' => :atenolol_50mg,
36
+ 'Atenolol (100mg tablet)' => :atenolol_100mg
37
+ }.freeze
38
+
39
+ def initialize(start_date:, end_date:, **_kwargs)
40
+ @start_date = ActiveRecord::Base.connection.quote(start_date)
41
+ @end_date = ActiveRecord::Base.connection.quote(end_date)
42
+ end
43
+
44
+ def find_report
45
+ @report = init_report
46
+ process_data
47
+ @report
48
+ rescue StandardError => e
49
+ puts e.message
50
+ puts e.backtrace.join("\n")
51
+ raise e
52
+ end
53
+
54
+ private
55
+
56
+ def init_report
57
+ AGE_GROUPS.each_with_object({}) do |age_group, report|
58
+ report[age_group] = GENDER.each_with_object({}) do |gender, gender_sub_report|
59
+ gender_sub_report[gender] = initialize_gender_metrics
60
+ end
61
+ end
62
+ end
63
+
64
+ # rubocop:disable Metrics/MethodLength
65
+ def initialize_gender_metrics
66
+ {
67
+ screened: [],
68
+ normal_reading: [],
69
+ mild_reading: [],
70
+ moderate_reading: [],
71
+ severe_reading: [],
72
+ hydrochlorothiazide_25mg: [],
73
+ amlodipine_5mg: [],
74
+ amlodipine_10mg: [],
75
+ enalapril_5mg: [],
76
+ enalapril_10mg: [],
77
+ atenolol_50mg: [],
78
+ atenolol_100mg: [],
79
+ total_regimen: []
80
+ }
81
+ end
82
+ # rubocop:enable Metrics/MethodLength
83
+
84
+ # rubocop:disable Metrics/AbcSize
85
+ # rubocop:disable Metrics/MethodLength
86
+ def process_data
87
+ (data || []).each do |row|
88
+ age_group = row['age_group']
89
+ gender = row['gender']
90
+ next unless AGE_GROUPS.include?(age_group)
91
+ next unless GENDER.include?(gender)
92
+
93
+ patient = client_info(row)
94
+ cluster = @report[age_group][gender]
95
+ cluster[:screened] << patient
96
+ process_bp_classification(cluster, patient, row['systolic_classification'], row['diastolic_classification'])
97
+ process_drug_data(cluster, patient, row['drug_names'].split(',')) if row['drug_names'].present?
98
+ cluster[:total_regimen] << patient if row['drug_names'].present?
99
+ end
100
+ end
101
+ # rubocop:enable Metrics/AbcSize
102
+ # rubocop:enable Metrics/MethodLength
103
+
104
+ def process_bp_classification(cluster, patient_id, sys_class, dia_class)
105
+ classification = SEVERITY_ORDER[sys_class.to_sym] > SEVERITY_ORDER[dia_class.to_sym] ? sys_class : dia_class
106
+ case SEVERITY_CLASSIFICATION[classification.to_sym]
107
+ when 'NORMAL'
108
+ cluster[:normal_reading] << patient_id
109
+ when 'MILD'
110
+ cluster[:mild_reading] << patient_id
111
+ when 'MODERATE'
112
+ cluster[:moderate_reading] << patient_id
113
+ when 'SEVERE'
114
+ cluster[:severe_reading] << patient_id
115
+ end
116
+ end
117
+
118
+ def process_drug_data(cluster, patient_id, drugs)
119
+ return if drugs.blank?
120
+
121
+ drugs.each do |drug|
122
+ cluster_key = DRUG_MAPPING[drug]
123
+ cluster[cluster_key] << patient_id if cluster_key
124
+ end
125
+ end
126
+
127
+ SEVERITY_ORDER = {
128
+ severe_reading: 4,
129
+ moderate_reading: 3,
130
+ mild_reading: 2,
131
+ normal_reading: 1
132
+ }.freeze
133
+
134
+ SEVERITY_CLASSIFICATION = {
135
+ severe_reading: 'SEVERE',
136
+ moderate_reading: 'MODERATE',
137
+ mild_reading: 'MILD',
138
+ normal_reading: 'NORMAL'
139
+ }.freeze
140
+
141
+ def data
142
+ ActiveRecord::Base.connection.select_all <<~SQL
143
+ SELECT
144
+ tpo.patient_id,
145
+ sys.value_numeric systolic,
146
+ dia.value_numeric diastolic,
147
+ CASE
148
+ WHEN sys.value_numeric <= 139 THEN 'normal_reading'
149
+ WHEN sys.value_numeric > 139 AND sys.value_numeric <= 159 THEN 'mild_reading'
150
+ WHEN sys.value_numeric > 159 AND sys.value_numeric <= 179 THEN 'moderate_reading'
151
+ WHEN sys.value_numeric > 179 THEN 'severe_reading'
152
+ END AS systolic_classification,
153
+ CASE
154
+ WHEN dia.value_numeric <= 89 THEN 'normal_reading'
155
+ WHEN dia.value_numeric > 89 AND dia.value_numeric <= 99 THEN 'mild_reading'
156
+ WHEN dia.value_numeric > 99 AND dia.value_numeric <= 109 THEN 'moderate_reading'
157
+ WHEN dia.value_numeric > 109 THEN 'severe_reading'
158
+ END AS diastolic_classification,
159
+ UPPER(LEFT(p.gender, 1)) gender,
160
+ disaggregated_age_group(p.birthdate, #{@end_date}) age_group,
161
+ i.identifier arv_number,
162
+ latest_drug_order.start_date,
163
+ GROUP_CONCAT(DISTINCT d.name) drug_names
164
+ FROM (
165
+ SELECT MAX(o.obs_datetime) obs_date, o.person_id patient_id
166
+ FROM obs o
167
+ INNER JOIN encounter e ON e.encounter_id = o.encounter_id AND e.voided = 0 AND e.program_id = 1 -- HIV PROGRAM
168
+ WHERE o.concept_id = 5085 -- Systolic blood pressure
169
+ AND o.voided = 0 AND o.obs_datetime >= #{@start_date} AND o.obs_datetime < #{@end_date} + INTERVAL 1 DAY
170
+ GROUP BY o.person_id
171
+ ) AS tpo
172
+ INNER JOIN obs sys ON sys.concept_id = 5085 AND sys.person_id = tpo.patient_id AND sys.obs_datetime = tpo.obs_date AND sys.voided = 0
173
+ INNER JOIN obs dia ON dia.concept_id = 5086 AND dia.person_id = tpo.patient_id AND dia.obs_datetime = tpo.obs_date AND dia.voided = 0
174
+ INNER JOIN person p ON p.person_id = tpo.patient_id AND p.voided = 0
175
+ INNER JOIN encounter e ON e.encounter_id = dia.encounter_id AND e.program_id = 1 AND e.voided = 0
176
+ LEFT JOIN patient_identifier i ON i.patient_id = e.patient_id AND i.identifier_type = 4 AND i.voided = 0
177
+ LEFT JOIN (
178
+ SELECT o.patient_id, MAX(o.start_date) start_date
179
+ FROM orders o
180
+ INNER JOIN drug_order dor ON dor.order_id = o.order_id AND dor.quantity > 0
181
+ WHERE o.voided = 0
182
+ AND o.concept_id IN (SELECT concept_id FROM concept_name WHERE name LIKE '%Hydrochlorothiazide%' OR name LIKE '%Amlodipine%' OR name LIKE '%Enalapril%' OR name LIKE '%Atenolol%')
183
+ AND o.start_date >= #{@start_date} AND o.start_date < #{@end_date} + INTERVAL 1 DAY
184
+ GROUP BY o.patient_id
185
+ ) AS latest_drug_order ON latest_drug_order.patient_id = tpo.patient_id AND DATE(latest_drug_order.start_date) >= DATE(tpo.obs_date)
186
+ LEFT JOIN orders ord ON ord.start_date = latest_drug_order.start_date AND ord.patient_id = latest_drug_order.patient_id AND ord.voided = 0 AND ord.concept_id IN (SELECT concept_id FROM concept_name WHERE name LIKE '%Hydrochlorothiazide%' OR name LIKE '%Amlodipine%' OR name LIKE '%Enalapril%' OR name LIKE '%Atenolol%')
187
+ LEFT JOIN drug_order dor ON dor.order_id = ord.order_id AND dor.quantity > 0
188
+ LEFT JOIN drug d ON d.drug_id = dor.drug_inventory_id AND d.retired = 0
189
+ WHERE tpo.patient_id NOT IN (#{external_clients})
190
+ GROUP BY tpo.patient_id
191
+ ORDER BY tpo.patient_id ASC
192
+ SQL
193
+ end
194
+
195
+ def external_clients
196
+ <<~SQL
197
+ SELECT obs.person_id FROM obs,
198
+ (SELECT person_id, Max(obs_datetime) AS obs_datetime, concept_id FROM obs
199
+ WHERE concept_id IN (SELECT concept_id FROM concept_name WHERE name = 'Type of patient' AND voided = 0)
200
+ AND DATE(obs_datetime) <= #{@end_date}
201
+ AND voided = 0
202
+ GROUP BY person_id) latest_record
203
+ WHERE obs.person_id = latest_record.person_id
204
+ AND obs.concept_id = latest_record.concept_id
205
+ AND obs.obs_datetime = latest_record.obs_datetime
206
+ AND obs.value_coded IN (SELECT concept_id FROM concept_name WHERE name = 'Drug refill' || name = 'External consultation')
207
+ AND obs.voided = 0
208
+ SQL
209
+ end
210
+
211
+ def client_info(data)
212
+ {
213
+ patient_id: data['patient_id'],
214
+ arv_number: data['arv_number'],
215
+ gender: data['gender'],
216
+ diastolic: data['diastolic'],
217
+ systolic: data['systolic']
218
+ }
219
+ end
220
+ end
221
+ # rubocop:enable Metrics/ClassLength
222
+ end
223
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Cohort report builder class.
4
+ #
5
+ # This class only provides one public method (start_build_report) besides
6
+ # the constructor. This method must be called to build report and save
7
+ # it to database.
8
+ module MalawiHivProgramReports
9
+ module Clinic
10
+ class IptCoverage
11
+ def initialize(start_date:, end_date:)
12
+ @start_date = start_date.to_date.strftime('%Y-%m-%d 00:00:00')
13
+ @end_date = end_date.to_date.strftime('%Y-%m-%d 23:59:59')
14
+ end
15
+
16
+ def data
17
+ patient_ids = []
18
+ patients = {}
19
+
20
+ (on_art_in_reporting_period || []).each do |data|
21
+ patient_ids << data['patient_id'].to_i
22
+ end
23
+
24
+ return patients if patient_ids.blank?
25
+
26
+ data = ipt_dispensations(patient_ids)
27
+
28
+ (data || []).each do |record|
29
+ patient_id = record['patient_id'].to_i
30
+ age_group = record['age_group']
31
+ gender = record['gender']
32
+
33
+ gender = if gender.blank?
34
+ 'Unknown'
35
+ else
36
+ (/F/i.match?(gender) ? 'Female' : 'Male')
37
+ end
38
+
39
+ patients[age_group] = {} if patients[age_group].blank?
40
+ patients[age_group][gender] = {} if patients[age_group][gender].blank?
41
+ patients[age_group][gender][patient_id] = 0 if patients[age_group][gender][patient_id].blank?
42
+
43
+ prescription_info = ActiveRecord::Base.connection.select_one <<-SQL
44
+ SELECT TIMESTAMPDIFF(day, DATE('#{record['start_date']}'), DATE('#{record['auto_expire_date']}')) days;
45
+ SQL
46
+
47
+ next if prescription_info['days'].blank?
48
+
49
+ patients[age_group][gender][patient_id] += prescription_info['days'].to_i
50
+ end
51
+
52
+ age_groups = {}
53
+
54
+ (patients || {}).each do |keys, values|
55
+ pats = values
56
+ (pats || {}).each do |sex, ids|
57
+ (ids || {}).each do |patient_id, count|
58
+ next unless count >= 168
59
+
60
+ age_groups[keys] = {} if age_groups[keys].blank?
61
+ age_groups[keys][sex] = [] if age_groups[keys][sex].blank?
62
+ age_groups[keys][sex] << patient_id
63
+ end
64
+ end
65
+ end
66
+
67
+ age_groups
68
+ end
69
+
70
+ private
71
+
72
+ def on_art_in_reporting_period
73
+ ActiveRecord::Base.connection.select_all <<-SQL
74
+ select
75
+ `p`.`patient_id` AS `patient_id`, pe.birthdate, pe.gender,
76
+ cast(patient_date_enrolled(`p`.`patient_id`) as date) AS `date_enrolled`
77
+ from
78
+ ((`patient_program` `p`
79
+ left join `person` `pe` ON ((`pe`.`person_id` = `p`.`patient_id`))
80
+ left join `patient_state` `s` ON ((`p`.`patient_program_id` = `s`.`patient_program_id`)))
81
+ left join `person` ON ((`person`.`person_id` = `p`.`patient_id`)))
82
+ where
83
+ ((`p`.`voided` = 0)
84
+ and (`s`.`voided` = 0)
85
+ and (`p`.`program_id` = 1)
86
+ and (`s`.`state` = 7))
87
+ /*and (DATE(`s`.`start_date`) BETWEEN '#{@start_date}' AND '#{@end_date}')*/
88
+ and pepfar_patient_outcome(p.patient_id, DATE('#{@end_date}')) = 'On antiretrovirals'
89
+ group by `p`.`patient_id`
90
+ HAVING date_enrolled IS NOT NULL;
91
+ SQL
92
+ end
93
+
94
+ def ipt_dispensations(patient_ids)
95
+ date_six_months_ago = (@start_date.to_date - 6.months).strftime('%Y-%m-%d 00:00:00')
96
+
97
+ ActiveRecord::Base.connection.select_all <<-SQL
98
+ SELECT
99
+ o.patient_id, birthdate, gender, o.start_date, o.auto_expire_date,
100
+ disaggregated_age_group(p.birthdate, DATE('#{@end_date}')) age_group
101
+ FROM person p
102
+ INNER JOIN orders o ON o.patient_id = p.person_id
103
+ INNER JOIN drug_order t ON o.order_id = t.order_id
104
+ INNER JOIN drug d ON d.drug_id = t.drug_inventory_id
105
+ WHERE o.voided = 0 AND (o.start_date BETWEEN '#{date_six_months_ago}' AND '#{@end_date}')
106
+ AND d.concept_id = 656 AND o.patient_id IN(#{patient_ids.join(',')})
107
+ AND t.quantity > 0 ORDER BY o.patient_id;
108
+ SQL
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MalawiHivProgramReports
4
+ module Clinic
5
+ class IptReport
6
+ def initialize(start_date:, end_date:)
7
+ @start_date = start_date
8
+ @end_date = end_date
9
+ end
10
+
11
+ def ipt_coverage
12
+ coverage
13
+ end
14
+
15
+ private
16
+
17
+ def coverage
18
+ program_id = ::Program.find_by_name('HIV PROGRAM').id
19
+ data = ActiveRecord::Base.connection.select_all <<~SQL
20
+ SELECT patient_id FROM encounter
21
+ WHERE encounter_type = #{::EncounterType.find_by_name('HIV Reception').id}
22
+ AND encounter_datetime BETWEEN '#{@start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
23
+ AND '#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}' AND voided = 0
24
+ AND program_id = #{program_id}
25
+ GROUP BY DATE(encounter_datetime), patient_id;
26
+ SQL
27
+
28
+ patient_ids = data.map { |p| p['patient_id'].to_i }
29
+ return { patients: 0, prescribed: 0, dispensed: 0 } if patient_ids.blank?
30
+
31
+ ipt_drugs = ::Drug.where('name LIKE (?)', '%Isoniazid%').map(&:drug_id)
32
+
33
+ data = ActiveRecord::Base.connection.select_one <<~SQL
34
+ SELECT count(o.patient_id) AS prescribed FROM orders o
35
+ INNER JOIN encounter e ON e.encounter_id = o.encounter_id
36
+ INNER JOIN drug_order i ON i.order_id = o.order_id
37
+ WHERE o.voided = 0 AND e.patient_id IN(#{patient_ids.join(',')})
38
+ AND start_date BETWEEN "#{@start_date.to_date.strftime('%Y-%m-%d 00:00:00')}"
39
+ AND "#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}"#{' '}
40
+ AND drug_inventory_id IN(#{ipt_drugs.join(',')})#{' '}
41
+ AND e.program_id = #{program_id};
42
+ SQL
43
+
44
+ #
45
+ # data = ::Order.where("start_date BETWEEN ? AND ? AND drug_inventory_id IN(?)
46
+ # AND program_id = ? AND orders.patient_id IN(?)",
47
+ # @start_date.to_date.strftime('%Y-%m-%d 00:00:00'),
48
+ # @end_date.to_date.strftime('%Y-%m-%d 23:59:59'), ipt_drugs, program_id, patient_ids).\
49
+ # joins("INNER JOIN encounter e ON e.encounter_id = orders.encounter_id
50
+ # INNER JOIN drug_order i ON i.order_id = orders.order_id").count(:patient_id)
51
+
52
+ data2 = ActiveRecord::Base.connection.select_one <<~SQL
53
+ SELECT count(o.patient_id) AS dispensed FROM orders o
54
+ INNER JOIN encounter e ON e.encounter_id = o.encounter_id
55
+ INNER JOIN drug_order i ON i.order_id = o.order_id
56
+ WHERE o.voided = 0 AND e.patient_id IN(#{patient_ids.join(',')})
57
+ AND start_date BETWEEN "#{@start_date.to_date.strftime('%Y-%m-%d 00:00:00')}"
58
+ AND "#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}"
59
+ AND drug_inventory_id IN(#{ipt_drugs.join(',')})
60
+ AND quantity > 0 AND e.program_id = #{program_id};
61
+ SQL
62
+
63
+ prescribed = data['prescribed']
64
+ dispensed = data2['dispensed']
65
+ { patients: patient_ids.length, prescribed:, dispensed: }
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This class generates the LIMS results report (results delivered electronically)
4
+ module MalawiHivProgramReports
5
+ module Clinic
6
+ class LimsResults
7
+ include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
8
+ attr_reader :start_date, :end_date
9
+
10
+ def initialize(start_date:, end_date:, **kwargs)
11
+ @start_date = start_date
12
+ @end_date = end_date
13
+ @occupation = kwargs[:occupation]
14
+ end
15
+
16
+ def find_report
17
+ data
18
+ end
19
+
20
+ private
21
+
22
+ def data
23
+ ActiveRecord::Base.connection.select_all <<~SQL
24
+ SELECT o.start_date AS date_ordered, pn.given_name, pn.family_name, pi.identifier AS arv_number, e.patient_id,
25
+ las.date_received, o.accession_number, CONCAT(COALESCE(res.value_modifier, '='), COALESCE(res.value_text, res.value_numeric)) AS result,
26
+ cn.name AS test_name, las.acknowledgement_type AS result_delivery_mode, statuses.value_text AS order_status, reason_test.name AS test_reason
27
+ FROM orders o
28
+ INNER JOIN lab_lims_order_mappings llom ON llom.order_id = o.order_id
29
+ LEFT JOIN lims_acknowledgement_statuses las ON las.order_id = o.order_id
30
+ INNER JOIN encounter e ON e.encounter_id = o.encounter_id AND e.voided = 0 AND (e.program_id = 1 OR e.program_id = 23) -- HIV PROGRAM AND Laboratory program
31
+ INNER JOIN users u ON u.user_id = o.orderer
32
+ INNER JOIN person_name pn ON pn.person_id = u.person_id
33
+ INNER JOIN obs test ON test.person_id = e.patient_id AND test.voided = 0 AND test.order_id = o.order_id AND test.concept_id = 9737 -- 'Test Type'
34
+ INNER JOIN concept_name cn ON cn.concept_id = test.value_coded AND cn.voided = 0 AND cn.locale_preferred = 1
35
+ LEFT JOIN patient_identifier pi ON pi.patient_id = e.patient_id AND pi.voided = 0 AND pi.identifier_type = #{identifier_type}
36
+ LEFT JOIN obs ON obs.person_id = e.patient_id AND obs.voided = 0 AND obs.order_id = o.order_id
37
+ AND obs.concept_id = 7363 -- 'Lab test result'
38
+ LEFT JOIN obs statuses ON statuses.order_id = o.order_id AND statuses.voided = 0 AND statuses.concept_id = 10682 -- 'lab order status'
39
+ LEFT JOIN obs res ON res.obs_group_id = obs.obs_id AND res.voided = 0 AND res.order_id = o.order_id
40
+ LEFT JOIN obs reason ON reason.order_id = o.order_id AND reason.voided = 0 AND reason.concept_id = 2429 -- 'Reason for test'
41
+ LEFT JOIN concept_name reason_test ON reason_test.concept_id = reason.value_coded AND reason_test.voided = 0
42
+ LEFT JOIN (#{current_occupation_query}) AS a ON a.person_id = e.patient_id
43
+ WHERE DATE(o.start_date) BETWEEN '#{start_date}' AND '#{end_date}' AND o.voided = 0 #{%w[Military Civilian].include?(@occupation) ? 'AND' : ''} #{occupation_filter(occupation: @occupation, field_name: 'value', table_name: 'a', include_clause: false)}
44
+ GROUP BY o.order_id
45
+ SQL
46
+ end
47
+
48
+ def identifier_type
49
+ filling_number = ::GlobalPropertyService.use_filing_numbers?
50
+
51
+ filling_number ? 17 : 4
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Outcome List Report
4
+ module MalawiHivProgramReports
5
+ module Clinic
6
+ class OutcomeList
7
+ include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
8
+
9
+ REPORTS = Set.new(%i[transfer_out died stopped]).freeze
10
+
11
+ def initialize(start_date:, end_date:, outcome:, **kwargs)
12
+ @start_date = start_date.to_date.strftime('%Y-%m-%d 00:00:00')
13
+ @end_date = end_date.to_date.strftime('%Y-%m-%d 23:59:59')
14
+ @occupation = kwargs[:occupation]
15
+ @report = load_report outcome.downcase.split.join('_')
16
+ end
17
+
18
+ def get_list
19
+ @report
20
+ end
21
+
22
+ private
23
+
24
+ def start_date
25
+ ActiveRecord::Base.connection.quote(@start_date)
26
+ end
27
+
28
+ def end_date
29
+ ActiveRecord::Base.connection.quote(@end_date)
30
+ end
31
+
32
+ def load_report(name)
33
+ name = name.to_sym
34
+
35
+ raise "Invalid report: #{name}" unless REPORTS.include?(name)
36
+
37
+ method(name).call
38
+ end
39
+
40
+ def transfer_out
41
+ outcome_query 'Patient transferred out'
42
+ end
43
+
44
+ def died
45
+ outcome_query 'Patient died'
46
+ end
47
+
48
+ def stopped
49
+ outcome_query 'Treatment stopped'
50
+ end
51
+
52
+ def outcome_query(outcome_state)
53
+ report_type = 'moh'
54
+ MalawiHivProgramReports::Moh::CohortBuilder.new(outcomes_definition: report_type).init_temporary_tables(
55
+ @start_date.to_date, @end_date.to_date, @occupation
56
+ )
57
+
58
+ transfer_out_to_location_sql = ''
59
+ transfer_out_to_location_name_sql = ''
60
+ if /Transfer/i.match?(outcome_state)
61
+ concept_id = ::ConceptName.find_by_name('Transfer out to location').concept_id
62
+ transfer_out_to_location_name_sql = ' ,to_location.value_text transferred_out_to'
63
+ transfer_out_to_location_sql = ' LEFT JOIN obs to_location ON to_location.person_id = e.patient_id'
64
+ transfer_out_to_location_sql += " AND to_location.concept_id = #{concept_id} AND to_location.voided = 0"
65
+ end
66
+
67
+ data = ActiveRecord::Base.connection.select_all <<~SQL
68
+ SELECT
69
+ e.patient_id, i.identifier, e.birthdate,
70
+ e.gender, n.given_name, n.family_name,
71
+ art_reason.name art_reason, a.value cell_number,
72
+ s3.state_province district, s3.county_district ta,
73
+ s3.city_village village, TIMESTAMPDIFF(year, DATE(e.birthdate), DATE('#{@end_date}')) age,
74
+ pp.date_enrolled, pp.date_completed,
75
+ s2.start_date outcome_date, s2.end_date, s2.state
76
+ #{transfer_out_to_location_name_sql}
77
+ FROM temp_earliest_start_date e
78
+ INNER JOIN temp_patient_outcomes o ON e.patient_id = o.patient_id
79
+ LEFT JOIN patient_identifier i ON i.patient_id = e.patient_id
80
+ AND i.voided = 0 AND i.identifier_type = 4
81
+ INNER JOIN person_name n ON n.person_id = e.patient_id AND n.voided = 0
82
+ LEFT JOIN person_attribute a ON a.person_id = e.patient_id
83
+ AND a.voided = 0 AND a.person_attribute_type_id = 12
84
+ LEFT JOIN person_address s3 ON s3.person_id = e.patient_id
85
+ LEFT JOIN concept_name art_reason ON art_reason.concept_id = e.reason_for_starting_art
86
+ #{transfer_out_to_location_sql}
87
+ INNER JOIN patient_program pp ON pp.patient_id = e.patient_id AND pp.program_id = 1
88
+ INNER JOIN patient_state s2 ON s2.patient_program_id = pp.patient_program_id
89
+ INNER JOIN program_workflow_state ws ON ws.program_workflow_state_id = s2.state
90
+ INNER JOIN program_workflow w ON w.program_workflow_id = ws.program_workflow_id
91
+ INNER JOIN concept_name n2 ON n2.concept_id = ws.concept_id
92
+ WHERE o.cum_outcome = '#{outcome_state}'
93
+ AND pp.voided = 0
94
+ AND s2.voided = 0
95
+ AND s2.start_date
96
+ BETWEEN '#{@start_date}' AND '#{@end_date}' AND s2.state NOT IN(7,1, 12)
97
+ GROUP BY e.patient_id ORDER BY e.patient_id, n.date_created DESC;
98
+ SQL
99
+
100
+ patients = []
101
+
102
+ (data || []).each do |person|
103
+ patients << {
104
+ patient_id: person['patient_id'],
105
+ given_name: person['given_name'],
106
+ family_name: person['family_name'],
107
+ birthdate: person['birthdate'],
108
+ gender: person['gender'],
109
+ arv_number: person['arv_number'],
110
+ outcome: 'Defaulted',
111
+ art_reason: person['art_reason'],
112
+ cell_number: person['cell_number'],
113
+ district: person['district'],
114
+ ta: person['ta'],
115
+ village: person['village'],
116
+ current_age: person['age'],
117
+ identifier: person['identifier'],
118
+ transferred_out_to: person['transferred_out_to'] || 'N/A',
119
+ outcome_date: person['outcome_date']&.to_date || 'N/A'
120
+ }
121
+ end
122
+
123
+ patients
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Returns all patients alive and on treatment within a given
4
+ # time period (Tx Curr).
5
+ module MalawiHivProgramReports
6
+ module Clinic
7
+ class PatientsAliveAndOnTreatment
8
+ attr_reader :start_date, :end_date
9
+
10
+ include MalawiHivProgramReports::Utils::ConcurrencyUtils
11
+
12
+ def initialize(start_date:, end_date:, outcomes_definition: 'moh', rebuild_outcomes: true, **kwargs)
13
+ @start_date = start_date
14
+ @end_date = end_date
15
+ @rebuild_outcomes = rebuild_outcomes
16
+ @outcomes_definition = outcomes_definition
17
+ @occupation = kwargs[:occupation]
18
+ @location = kwargs[:location]
19
+ end
20
+
21
+ ##
22
+ # Repopulates temp_patient_outcomes and temp_earliest_start_date tables.
23
+ #
24
+ # The data in temp_patient_outcomes can be used to quickly find patients
25
+ # with various outcomes.
26
+ def refresh_outcomes_table
27
+ logger.debug('Initialising cohort temporary tables...')
28
+ MalawiHivProgramReports::Moh::CohortBuilder.new(outcomes_definition: @outcomes_definition, location: @location)
29
+ .init_temporary_tables(@start_date, @end_date, @occupation)
30
+ end
31
+
32
+ def find_report
33
+ query.map(&:patient_id)
34
+ end
35
+
36
+ def query
37
+ with_lock(Cohort::LOCK_FILE) do
38
+ refresh_outcomes_table if @rebuild_outcomes || !outcomes_table_exists?
39
+
40
+ ::Patient.find_by_sql <<~SQL
41
+ SELECT patient_id FROM temp_patient_outcomes WHERE cum_outcome LIKE 'On antiretrovirals'
42
+ SQL
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def outcomes_table_exists?
49
+ ActiveRecord::Base.connection.table_exists?(:temp_patient_outcomes)
50
+ end
51
+
52
+ def logger
53
+ Rails.logger
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MalawiHivProgramReports
4
+ module Clinic
5
+ class PatientsDueForViralLoad
6
+ attr_accessor :start_date, :end_date
7
+
8
+ def initialize(start_date:, end_date:, **_kwargs)
9
+ @start_date = start_date
10
+ @end_date = end_date
11
+ end
12
+
13
+ def find_report
14
+ patients_alive_and_on_treatment.select do |patient_id|
15
+ patient_viral_load_due?(patient_id)
16
+ end
17
+ end
18
+
19
+ private
20
+
21
+ def patient_viral_load_due?(patient_id)
22
+ reminder = viral_load_reminder(patient_id)
23
+ reminder[:eligibile] # Yes its spelt eligibile not eligible...
24
+ end
25
+
26
+ def patients_alive_and_on_treatment
27
+ PatientsAliveAndOnTreatment
28
+ .new(start_date:, end_date:)
29
+ .find_report
30
+ end
31
+
32
+ def viral_load_reminder(patient_id)
33
+ ArtService::VlReminder
34
+ .new(patient_id:, date: end_date)
35
+ .vl_reminder_info
36
+ end
37
+ end
38
+ end
39
+ end