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.
- checksums.yaml +4 -4
- data/app/services/malawi_hiv_program_reports/README.md +16 -0
- data/app/services/malawi_hiv_program_reports/adapters/moh/custom.rb +199 -0
- data/app/services/malawi_hiv_program_reports/archiving_candidates.rb +130 -0
- data/app/services/malawi_hiv_program_reports/arv_refill_periods.rb +311 -0
- data/app/services/malawi_hiv_program_reports/clinic/README.md +5 -0
- data/app/services/malawi_hiv_program_reports/clinic/appointments_report.rb +317 -0
- data/app/services/malawi_hiv_program_reports/clinic/discrepancy_report.rb +42 -0
- data/app/services/malawi_hiv_program_reports/clinic/docs/hypertension_report.md +31 -0
- data/app/services/malawi_hiv_program_reports/clinic/drug_dispensations.rb +48 -0
- data/app/services/malawi_hiv_program_reports/clinic/external_consultation_clients.rb +69 -0
- data/app/services/malawi_hiv_program_reports/clinic/hypertension_report.rb +223 -0
- data/app/services/malawi_hiv_program_reports/clinic/ipt_coverage.rb +112 -0
- data/app/services/malawi_hiv_program_reports/clinic/ipt_report.rb +69 -0
- data/app/services/malawi_hiv_program_reports/clinic/lims_results.rb +55 -0
- data/app/services/malawi_hiv_program_reports/clinic/outcome_list.rb +127 -0
- data/app/services/malawi_hiv_program_reports/clinic/patients_alive_and_on_treatment.rb +57 -0
- data/app/services/malawi_hiv_program_reports/clinic/patients_due_for_viral_load.rb +39 -0
- data/app/services/malawi_hiv_program_reports/clinic/patients_on_antiretrovirals.rb +44 -0
- data/app/services/malawi_hiv_program_reports/clinic/patients_on_dtg.rb +36 -0
- data/app/services/malawi_hiv_program_reports/clinic/patients_on_treatment.rb +42 -0
- data/app/services/malawi_hiv_program_reports/clinic/patients_with_outdated_demographics.rb +173 -0
- data/app/services/malawi_hiv_program_reports/clinic/pregnant_patients.rb +91 -0
- data/app/services/malawi_hiv_program_reports/clinic/regimen_dispensation_data.rb +282 -0
- data/app/services/malawi_hiv_program_reports/clinic/regimen_switch.rb +456 -0
- data/app/services/malawi_hiv_program_reports/clinic/regimens_and_formulations.rb +182 -0
- data/app/services/malawi_hiv_program_reports/clinic/regimens_by_weight_and_gender.rb +108 -0
- data/app/services/malawi_hiv_program_reports/clinic/retention.rb +246 -0
- data/app/services/malawi_hiv_program_reports/clinic/stock_card_report.rb +65 -0
- data/app/services/malawi_hiv_program_reports/clinic/tpt_outcome.rb +494 -0
- data/app/services/malawi_hiv_program_reports/clinic/tx_rtt.rb +169 -0
- data/app/services/malawi_hiv_program_reports/clinic/viral_load.rb +292 -0
- data/app/services/malawi_hiv_program_reports/clinic/viral_load_disaggregated.rb +97 -0
- data/app/services/malawi_hiv_program_reports/clinic/viral_load_results.rb +175 -0
- data/app/services/malawi_hiv_program_reports/clinic/visits_report.rb +113 -0
- data/app/services/malawi_hiv_program_reports/clinic/vl_collection.rb +48 -0
- data/app/services/malawi_hiv_program_reports/cohort/outcomes.rb +338 -0
- data/app/services/malawi_hiv_program_reports/cohort/regimens.rb +69 -0
- data/app/services/malawi_hiv_program_reports/cohort/side_effects.rb +141 -0
- data/app/services/malawi_hiv_program_reports/cohort/tpt.rb +172 -0
- data/app/services/malawi_hiv_program_reports/moh/cohort.rb +278 -0
- data/app/services/malawi_hiv_program_reports/moh/cohort_builder.rb +2340 -0
- data/app/services/malawi_hiv_program_reports/moh/cohort_disaggregated.rb +608 -0
- data/app/services/malawi_hiv_program_reports/moh/cohort_disaggregated_additions.rb +208 -0
- data/app/services/malawi_hiv_program_reports/moh/cohort_disaggregated_builder.rb +526 -0
- data/app/services/malawi_hiv_program_reports/moh/cohort_struct.rb +219 -0
- data/app/services/malawi_hiv_program_reports/moh/cohort_survival_analysis.rb +203 -0
- data/app/services/malawi_hiv_program_reports/moh/moh_tpt.rb +223 -0
- data/app/services/malawi_hiv_program_reports/moh/tpt_newly_initiated.rb +235 -0
- data/app/services/malawi_hiv_program_reports/pepfar/defaulter_list.rb +25 -0
- data/app/services/malawi_hiv_program_reports/pepfar/maternal_status.rb +29 -0
- data/app/services/malawi_hiv_program_reports/pepfar/patient_start_vl.rb +45 -0
- data/app/services/malawi_hiv_program_reports/pepfar/regimen_switch.rb +479 -0
- data/app/services/malawi_hiv_program_reports/pepfar/sc_arvdisp.rb +174 -0
- data/app/services/malawi_hiv_program_reports/pepfar/sc_curr.rb +98 -0
- data/app/services/malawi_hiv_program_reports/pepfar/tb_prev.rb +163 -0
- data/app/services/malawi_hiv_program_reports/pepfar/tb_prev2.rb +222 -0
- data/app/services/malawi_hiv_program_reports/pepfar/tb_prev3.rb +421 -0
- data/app/services/malawi_hiv_program_reports/pepfar/tpt_status.rb +181 -0
- data/app/services/malawi_hiv_program_reports/pepfar/tx_ml.rb +181 -0
- data/app/services/malawi_hiv_program_reports/pepfar/tx_new.rb +202 -0
- data/app/services/malawi_hiv_program_reports/pepfar/tx_rtt.rb +288 -0
- data/app/services/malawi_hiv_program_reports/pepfar/tx_tb.rb +283 -0
- data/app/services/malawi_hiv_program_reports/pepfar/utils.rb +141 -0
- data/app/services/malawi_hiv_program_reports/pepfar/viral_load_coverage.rb +414 -0
- data/app/services/malawi_hiv_program_reports/pepfar/viral_load_coverage2.rb +433 -0
- data/app/services/malawi_hiv_program_reports/report_map.rb +56 -0
- data/app/services/malawi_hiv_program_reports/utils/README.md +8 -0
- data/app/services/malawi_hiv_program_reports/utils/common_sql_query_utils.rb +60 -0
- data/app/services/malawi_hiv_program_reports/utils/concurrency_utils.rb +53 -0
- data/app/services/malawi_hiv_program_reports/utils/docs/common_sql_query_utils.md +53 -0
- data/app/services/malawi_hiv_program_reports/utils/model_utils.rb +66 -0
- data/app/services/malawi_hiv_program_reports/utils/parameter_utils.rb +32 -0
- data/app/services/malawi_hiv_program_reports/utils/time_utils.rb +52 -0
- data/lib/malawi_hiv_program_reports/version.rb +1 -1
- metadata +74 -1
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MalawiHivProgramReports
|
|
4
|
+
module Pepfar
|
|
5
|
+
# TxTb report
|
|
6
|
+
# rubocop:disable Metrics/ClassLength
|
|
7
|
+
class TxTb
|
|
8
|
+
attr_accessor :start_date, :end_date, :report, :rebuild_outcome
|
|
9
|
+
|
|
10
|
+
include Utils
|
|
11
|
+
|
|
12
|
+
def initialize(start_date:, end_date:, **kwargs)
|
|
13
|
+
@start_date = start_date.to_date.strftime('%Y-%m-%d 00:00:00')
|
|
14
|
+
@end_date = end_date.to_date.strftime('%Y-%m-%d 23:59:59')
|
|
15
|
+
@rebuild_outcome = ActiveModel::Type::Boolean.new.cast(kwargs[:rebuild_outcome]) || false
|
|
16
|
+
@occupation = kwargs[:occupation]
|
|
17
|
+
@location = kwargs[:location]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def find_report
|
|
21
|
+
drop_temporary_tables
|
|
22
|
+
create_temp_earliest_start_date unless temp_eartliest_start_date_exists?
|
|
23
|
+
init_report
|
|
24
|
+
build_cohort_tables
|
|
25
|
+
process_tb_screening
|
|
26
|
+
process_tb_confirmed_and_on_treatment
|
|
27
|
+
process_patients_alive_and_on_art
|
|
28
|
+
process_tb_screened
|
|
29
|
+
process_tb_confirmed
|
|
30
|
+
report
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def init_report
|
|
36
|
+
@report = initialize_report_structure
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def initialize_report_structure
|
|
40
|
+
pepfar_age_groups.each_with_object({}) do |age_group, report|
|
|
41
|
+
genders = %i[M F]
|
|
42
|
+
genders.each do |gender|
|
|
43
|
+
report[age_group] ||= {}
|
|
44
|
+
report[age_group][gender] = initialize_gender_metrics
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def initialize_gender_metrics
|
|
50
|
+
{
|
|
51
|
+
tx_curr: [],
|
|
52
|
+
symptom_screen_alone: [],
|
|
53
|
+
cxr_screen: [],
|
|
54
|
+
mwrd_screen: [],
|
|
55
|
+
sceen_pos_new: [],
|
|
56
|
+
sceen_neg_new: [],
|
|
57
|
+
started_tb_new: [],
|
|
58
|
+
sceen_pos_prev: [],
|
|
59
|
+
sceen_neg_prev: [],
|
|
60
|
+
started_tb_prev: []
|
|
61
|
+
}
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def drop_temporary_tables
|
|
65
|
+
execute_action('DROP TABLE IF EXISTS temp_tb_screened;')
|
|
66
|
+
execute_action('DROP TABLE IF EXISTS temp_tb_confirmed_and_on_treatment;')
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def build_cohort_tables
|
|
70
|
+
return unless rebuild_outcome || @occupation.present?
|
|
71
|
+
|
|
72
|
+
cohort_builder = MalawiHivProgramReports::Moh::CohortBuilder.new(outcomes_definition: 'pepfar', location: @location)
|
|
73
|
+
cohort_builder.init_temporary_tables(start_date, end_date, @occupation)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def process_tb_screening
|
|
77
|
+
execute_action(create_temp_tb_screened_query)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def create_temp_tb_screened_query
|
|
81
|
+
<<~SQL
|
|
82
|
+
CREATE TABLE temp_tb_screened AS
|
|
83
|
+
SELECT
|
|
84
|
+
o.person_id as patient_id,
|
|
85
|
+
LEFT(current_obs.gender, 1) AS gender, MAX(o.obs_datetime) AS screened_date,
|
|
86
|
+
current_obs.earliest_start_date as enrollment_date,
|
|
87
|
+
disaggregated_age_group(current_obs.birthdate, DATE('#{end_date.to_date}')) AS age_group,
|
|
88
|
+
cn.name AS tb_status,
|
|
89
|
+
GROUP_CONCAT(DISTINCT vcn.name) AS screening_methods
|
|
90
|
+
FROM obs o
|
|
91
|
+
INNER JOIN (
|
|
92
|
+
SELECT o.person_id, MAX(o.obs_datetime) AS obs_datetime, tesd.earliest_start_date, tesd.gender, tesd.birthdate
|
|
93
|
+
FROM obs o
|
|
94
|
+
INNER JOIN temp_earliest_start_date tesd ON tesd.patient_id = o.person_id
|
|
95
|
+
WHERE o.concept_id = #{::ConceptName.find_by_name('TB status').concept_id}
|
|
96
|
+
AND o.value_coded IN (SELECT concept_id FROM concept_name WHERE name IN ('TB Suspected', 'TB NOT suspected') AND voided = 0)
|
|
97
|
+
AND o.voided = 0 AND o.obs_datetime BETWEEN '#{start_date}' AND '#{end_date}'
|
|
98
|
+
GROUP BY o.person_id
|
|
99
|
+
) current_obs ON current_obs.person_id = o.person_id AND current_obs.obs_datetime = o.obs_datetime
|
|
100
|
+
INNER JOIN concept_name cn ON cn.concept_id = o.value_coded AND cn.voided = 0
|
|
101
|
+
LEFT JOIN obs screen_method ON screen_method.concept_id = #{::ConceptName.find_by_name('TB screening method used').concept_id} AND screen_method.voided = 0 AND screen_method.person_id = o.person_id AND DATE(screen_method.obs_datetime) = DATE(current_obs.obs_datetime)
|
|
102
|
+
LEFT JOIN concept_name vcn ON vcn.concept_id = screen_method.value_coded AND vcn.voided = 0 AND vcn.name IN ('CXR', 'MWRD')
|
|
103
|
+
WHERE o.concept_id = #{::ConceptName.find_by_name('TB status').concept_id}
|
|
104
|
+
AND o.voided = 0
|
|
105
|
+
AND o.value_coded IN (SELECT concept_id FROM concept_name WHERE name IN ('TB Suspected', 'TB NOT suspected') AND voided = 0)
|
|
106
|
+
AND o.obs_datetime BETWEEN '#{start_date}' AND '#{end_date}'
|
|
107
|
+
GROUP BY o.person_id
|
|
108
|
+
SQL
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def temp_eartliest_start_date_exists?
|
|
112
|
+
ActiveRecord::Base.connection.table_exists?('temp_earliest_start_date')
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def create_temp_earliest_start_date
|
|
116
|
+
cohort_builder = MalawiHivProgramReports::Moh::CohortBuilder.new(outcomes_definition: 'pepfar', location: @location)
|
|
117
|
+
cohort_builder.init_temporary_tables(start_date, end_date, @occupation)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def process_tb_confirmed_and_on_treatment
|
|
121
|
+
execute_action(create_temp_tb_confirmed_query)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def create_temp_tb_confirmed_query
|
|
125
|
+
<<~SQL
|
|
126
|
+
CREATE TABLE temp_tb_confirmed_and_on_treatment AS
|
|
127
|
+
SELECT
|
|
128
|
+
o.person_id as patient_id,
|
|
129
|
+
LEFT(p.gender, 1) AS gender,
|
|
130
|
+
disaggregated_age_group(p.birthdate, DATE('#{end_date.to_date}')) AS age_group,
|
|
131
|
+
COALESCE(MIN(tcd.value_datetime),MIN(o.obs_datetime)) AS tb_confirmed_date,
|
|
132
|
+
CASE
|
|
133
|
+
WHEN COUNT(tcd.value_datetime) > 0 THEN TRUE
|
|
134
|
+
ELSE FALSE
|
|
135
|
+
END AS has_tb_confirmed_date,
|
|
136
|
+
tesd.earliest_start_date as enrollment_date,
|
|
137
|
+
prev.tb_confirmed_date prev_reading
|
|
138
|
+
FROM obs o
|
|
139
|
+
INNER JOIN temp_earliest_start_date tesd ON tesd.patient_id = o.person_id
|
|
140
|
+
INNER JOIN person p ON p.person_id = o.person_id AND p.voided = 0
|
|
141
|
+
LEFT JOIN obs tcd ON tcd.concept_id = #{::ConceptName.find_by_name('TB treatment start date').concept_id} AND tcd.voided = 0 AND tcd.person_id = o.person_id
|
|
142
|
+
LEFT JOIN (
|
|
143
|
+
SELECT
|
|
144
|
+
o.person_id,
|
|
145
|
+
COALESCE(MAX(tcd.value_datetime),MAX(o.obs_datetime)) AS tb_confirmed_date
|
|
146
|
+
FROM obs o
|
|
147
|
+
LEFT JOIN obs tcd ON tcd.concept_id = #{::ConceptName.find_by_name('TB treatment start date').concept_id} AND tcd.voided = 0 AND tcd.person_id = o.person_id
|
|
148
|
+
WHERE o.concept_id = #{::ConceptName.find_by_name('TB status').concept_id}
|
|
149
|
+
AND o.value_coded = #{::ConceptName.find_by_name('Confirmed TB on treatment').concept_id}
|
|
150
|
+
AND o.voided = 0
|
|
151
|
+
AND o.obs_datetime <= '#{start_date}'
|
|
152
|
+
GROUP BY o.person_id
|
|
153
|
+
) prev ON prev.person_id = o.person_id
|
|
154
|
+
WHERE o.concept_id = #{::ConceptName.find_by_name('TB status').concept_id}
|
|
155
|
+
AND o.value_coded = #{::ConceptName.find_by_name('Confirmed TB on treatment').concept_id}
|
|
156
|
+
AND o.voided = 0
|
|
157
|
+
AND o.obs_datetime BETWEEN '#{start_date}' AND '#{end_date}'
|
|
158
|
+
GROUP BY o.person_id
|
|
159
|
+
SQL
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def process_patients_alive_and_on_art
|
|
163
|
+
find_patients_alive_and_on_art.each do |patient|
|
|
164
|
+
next unless pepfar_age_groups.include?(patient['age_group'])
|
|
165
|
+
|
|
166
|
+
@report[patient['age_group']][patient['gender'].to_sym][:tx_curr] << patient['patient_id']
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
def find_patients_alive_and_on_art
|
|
171
|
+
execute_query(create_patients_alive_and_on_art_query)
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def create_patients_alive_and_on_art_query
|
|
175
|
+
<<~SQL
|
|
176
|
+
SELECT tpo.patient_id, LEFT(tesd.gender, 1) AS gender, disaggregated_age_group(tesd.birthdate, DATE('#{end_date.to_date}')) age_group
|
|
177
|
+
FROM temp_patient_outcomes tpo
|
|
178
|
+
INNER JOIN temp_earliest_start_date tesd ON tesd.patient_id = tpo.patient_id
|
|
179
|
+
WHERE tpo.cum_outcome = 'On antiretrovirals'
|
|
180
|
+
SQL
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# rubocop:disable Metrics/AbcSize
|
|
184
|
+
# rubocop:disable Metrics/MethodLength
|
|
185
|
+
def process_tb_screened
|
|
186
|
+
tb_screened_data = find_tb_screened_data
|
|
187
|
+
tb_screened_data.each do |patient|
|
|
188
|
+
age_group = patient['age_group']
|
|
189
|
+
next unless pepfar_age_groups.include?(age_group)
|
|
190
|
+
|
|
191
|
+
gender = patient['gender'].to_sym
|
|
192
|
+
metrics = @report[age_group][gender]
|
|
193
|
+
enrollment_date = patient['enrollment_date']
|
|
194
|
+
tb_status = patient['tb_status'].downcase
|
|
195
|
+
screening_methods = patient['screening_methods']&.split(',')&.map(&:downcase)
|
|
196
|
+
patient_id = patient['patient_id']
|
|
197
|
+
process_method(metrics, screening_methods, patient_id)
|
|
198
|
+
process_screening_results(metrics, enrollment_date, tb_status, patient_id)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def process_tb_confirmed
|
|
203
|
+
find_tb_confirmed_data.each do |patient|
|
|
204
|
+
age_group = patient['age_group']
|
|
205
|
+
next unless pepfar_age_groups.include?(age_group)
|
|
206
|
+
|
|
207
|
+
gender = patient['gender'].to_sym
|
|
208
|
+
metrics = @report[age_group][gender]
|
|
209
|
+
enrollment_date = patient['enrollment_date']
|
|
210
|
+
|
|
211
|
+
if new_on_art(enrollment_date)
|
|
212
|
+
metrics[:started_tb_new] << patient['patient_id']
|
|
213
|
+
else
|
|
214
|
+
metrics[:started_tb_prev] << patient['patient_id']
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def process_screening_results(metrics, enrollment_date, tb_status, patient_id)
|
|
220
|
+
if new_on_art(enrollment_date)
|
|
221
|
+
metrics[:sceen_pos_new] << patient_id if ['tb suspected', 'sup', 'confirmed tb not on treatment', 'norx',
|
|
222
|
+
'confirmed tb on treatment', 'rx'].include?(tb_status)
|
|
223
|
+
metrics[:sceen_neg_new] << patient_id if ['tb not suspected', 'nosup'].include?(tb_status)
|
|
224
|
+
else
|
|
225
|
+
metrics[:sceen_pos_prev] << patient_id if ['tb suspected', 'sup', 'confirmed tb not on treatment',
|
|
226
|
+
'norx'].include?(tb_status)
|
|
227
|
+
metrics[:sceen_neg_prev] << patient_id if ['tb not suspected', 'nosup'].include?(tb_status)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def process_method(metrics, methods, patient_id)
|
|
232
|
+
metrics[:symptom_screen_alone] << patient_id if methods.blank?
|
|
233
|
+
metrics[:cxr_screen] << patient_id if methods&.include?('cxr') && !methods&.include?('mwrd')
|
|
234
|
+
metrics[:mwrd_screen] << patient_id if methods&.include?('mwrd')
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
# rubocop:enable Metrics/AbcSize
|
|
238
|
+
# rubocop:enable Metrics/MethodLength
|
|
239
|
+
|
|
240
|
+
def execute_query(query)
|
|
241
|
+
ActiveRecord::Base.connection.select_all(query)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def execute_action(query)
|
|
245
|
+
ActiveRecord::Base.connection.execute(query)
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def find_tb_screened_data
|
|
249
|
+
execute_query(find_tb_screened_data_query)
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def find_tb_confirmed_data
|
|
253
|
+
execute_query(find_tb_confirmed_data_query)
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
def find_tb_method_data
|
|
257
|
+
execute_query(find_tb_method_data_query)
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def find_tb_screened_data_query
|
|
261
|
+
<<~SQL
|
|
262
|
+
SELECT tbs.patient_id, tbs.enrollment_date, LEFT(tbs.gender, 1) AS gender, tbs.age_group, tbs.tb_status, tbs.screened_date, tbs.screening_methods
|
|
263
|
+
FROM temp_tb_screened tbs
|
|
264
|
+
SQL
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def find_tb_confirmed_data_query
|
|
268
|
+
<<~SQL
|
|
269
|
+
SELECT t.patient_id, t.gender, t.age_group, t.enrollment_date, t.tb_confirmed_date
|
|
270
|
+
FROM temp_tb_confirmed_and_on_treatment t
|
|
271
|
+
WHERE t.tb_confirmed_date > '#{start_date}'
|
|
272
|
+
AND (t.has_tb_confirmed_date = TRUE OR t.prev_reading IS NULL OR TIMESTAMPDIFF(MONTH,t.prev_reading, t.tb_confirmed_date) > 6)
|
|
273
|
+
SQL
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
def new_on_art(earliest_start_date)
|
|
277
|
+
six_months_later = earliest_start_date.to_date + 6.months
|
|
278
|
+
six_months_later > end_date.to_date
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
# rubocop:enable Metrics/ClassLength
|
|
282
|
+
end
|
|
283
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MalawiHivProgramReports
|
|
4
|
+
module Pepfar
|
|
5
|
+
##
|
|
6
|
+
# Common utilities for Pepfar reports
|
|
7
|
+
module Utils
|
|
8
|
+
##
|
|
9
|
+
# An array of all groups as required by Pepfar.
|
|
10
|
+
def pepfar_age_groups
|
|
11
|
+
@pepfar_age_groups ||= [
|
|
12
|
+
'Unknown',
|
|
13
|
+
'<1 year',
|
|
14
|
+
'1-4 years', '5-9 years',
|
|
15
|
+
'10-14 years', '15-19 years',
|
|
16
|
+
'20-24 years',
|
|
17
|
+
'25-29 years', '30-34 years',
|
|
18
|
+
'35-39 years', '40-44 years',
|
|
19
|
+
'45-49 years', '50-54 years',
|
|
20
|
+
'55-59 years', '60-64 years',
|
|
21
|
+
'65-69 years', '70-74 years',
|
|
22
|
+
'75-79 years', '80-84 years',
|
|
23
|
+
'85-89 years',
|
|
24
|
+
'90 plus years'
|
|
25
|
+
].freeze
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# Returns the drilldown information for all specified patients (ie patient_ids)
|
|
30
|
+
#
|
|
31
|
+
# Information returned for a patient is as follows:
|
|
32
|
+
# * patient_id
|
|
33
|
+
# * arv_number or filing_number
|
|
34
|
+
# * age_group
|
|
35
|
+
# * birthdate
|
|
36
|
+
# * gender
|
|
37
|
+
def pepfar_patient_drilldown_information(patients, current_date)
|
|
38
|
+
raise ::ArgumentError, "current_date can't be nil" unless current_date
|
|
39
|
+
|
|
40
|
+
::Person.joins("LEFT JOIN patient_identifier ON patient_identifier.patient_id = person.person_id
|
|
41
|
+
AND patient_identifier.voided = 0
|
|
42
|
+
AND patient_identifier.identifier_type IN (#{pepfar_patient_identifier_type.to_sql})")
|
|
43
|
+
.where(person_id: patients)
|
|
44
|
+
.select("person.person_id AS patient_id,
|
|
45
|
+
person.gender,
|
|
46
|
+
person.birthdate,
|
|
47
|
+
disaggregated_age_group(person.birthdate, DATE(#{ActiveRecord::Base.connection.quote(current_date)})) AS age_group,
|
|
48
|
+
patient_identifier.identifier AS arv_number")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
##
|
|
52
|
+
# Returns the preferred Pepfar identifier type.
|
|
53
|
+
#
|
|
54
|
+
# In some clinics like Lighthouse Filing numbers are used exclusively and in other
|
|
55
|
+
# sites, ARV Numbers are used.
|
|
56
|
+
def pepfar_patient_identifier_type
|
|
57
|
+
name = GlobalProperty.find_by(property: 'use.filing.numbers')&.property_value
|
|
58
|
+
name = (name.present? && name == 'true') ? 'Filing number' : 'ARV Number'
|
|
59
|
+
::PatientIdentifierType.where(name:).select(:patient_identifier_type_id)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
FULL_6H_COURSE_PILLS = 146
|
|
63
|
+
FULL_3HP_COURSE_DAYS = 12.days
|
|
64
|
+
# NOTE: Arrived at 12 days above from how 3HP is prescribed. 1st time prescription
|
|
65
|
+
# A patient takes 3HP once every week. Therefore it is 4 times a months
|
|
66
|
+
# Multiply that with 3 months we arrive at 12
|
|
67
|
+
# Hence the patient is taking this drug 12 times to be considered complete on
|
|
68
|
+
# 3HP
|
|
69
|
+
|
|
70
|
+
##
|
|
71
|
+
# Returns whether a patient completed their course of TPT
|
|
72
|
+
def patient_completed_tpt?(patient, tpt)
|
|
73
|
+
if tpt == '3HP'
|
|
74
|
+
# return true if patient['total_days_on_medication'].to_i >= 83 # 3 months
|
|
75
|
+
return true if patient['months_on_tpt'].to_i >= 3
|
|
76
|
+
|
|
77
|
+
divider = patient['drug_concepts'].split(',').length > 1 ? 14.0 : 7.0
|
|
78
|
+
days_on_medication = (patient['total_days_on_medication'] / divider).round
|
|
79
|
+
days_on_medication.days >= FULL_3HP_COURSE_DAYS
|
|
80
|
+
else
|
|
81
|
+
patient['total_days_on_medication'].to_i >= FULL_6H_COURSE_PILLS
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# this just gives all clients who are truly external or drug refill
|
|
86
|
+
# rubocop:disable Metrics/MethodLength
|
|
87
|
+
# rubocop:disable Metrics/AbcSize
|
|
88
|
+
def drug_refills_and_external_consultation_list
|
|
89
|
+
to_remove = [0]
|
|
90
|
+
|
|
91
|
+
type_of_patient_concept = ::ConceptName.find_by_name('Type of patient').concept_id
|
|
92
|
+
new_patient_concept = ::ConceptName.find_by_name('New patient').concept_id
|
|
93
|
+
drug_refill_concept = ::ConceptName.find_by_name('Drug refill').concept_id
|
|
94
|
+
external_concept = ::ConceptName.find_by_name('External consultation').concept_id
|
|
95
|
+
hiv_clinic_registration_id = ::EncounterType.find_by_name('HIV CLINIC REGISTRATION').encounter_type_id
|
|
96
|
+
|
|
97
|
+
ActiveRecord::Base.connection.select_all("
|
|
98
|
+
SELECT p.person_id patient_id
|
|
99
|
+
FROM person p
|
|
100
|
+
INNER JOIN patient_program pp ON pp.patient_id = p.person_id AND pp.program_id = #{::Program.find_by_name('HIV PROGRAM').id} AND pp.voided = 0
|
|
101
|
+
INNER JOIN patient_state ps ON ps.patient_program_id = pp.patient_program_id AND ps.state = 7 AND ps.start_date IS NOT NULL
|
|
102
|
+
#{site_manager(operator: 'AND', column: 'pp.site_id', location: @location)}
|
|
103
|
+
LEFT JOIN encounter as hiv_registration ON hiv_registration.patient_id = p.person_id AND hiv_registration.encounter_datetime < DATE(#{ActiveRecord::Base.connection.quote(end_date)}) AND hiv_registration.encounter_type = #{hiv_clinic_registration_id} AND hiv_registration.voided = 0
|
|
104
|
+
#{site_manager(operator: 'AND', column: 'hiv_registration.site_id', location: @location)}
|
|
105
|
+
LEFT JOIN (SELECT * FROM obs WHERE concept_id = #{type_of_patient_concept} AND voided = 0 AND value_coded = #{new_patient_concept} AND obs_datetime < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
|
|
106
|
+
#{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}) AS new_patient ON p.person_id = new_patient.person_id
|
|
107
|
+
LEFT JOIN (SELECT * FROM obs WHERE concept_id = #{type_of_patient_concept} AND voided = 0 AND value_coded = #{drug_refill_concept} AND obs_datetime < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')} #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}) AS refill ON p.person_id = refill.person_id
|
|
108
|
+
LEFT JOIN (SELECT * FROM obs WHERE concept_id = #{type_of_patient_concept} AND voided = 0 AND value_coded = #{external_concept} AND obs_datetime < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')} #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}) AS external ON p.person_id = external.person_id
|
|
109
|
+
WHERE (refill.value_coded IS NOT NULL OR external.value_coded IS NOT NULL)
|
|
110
|
+
AND NOT (hiv_registration.encounter_id IS NOT NULL OR new_patient.value_coded IS NOT NULL)
|
|
111
|
+
#{site_manager(operator: 'AND', column: 'p.site_id', location: @location)}
|
|
112
|
+
GROUP BY p.person_id
|
|
113
|
+
ORDER BY hiv_registration.encounter_datetime DESC, refill.obs_datetime DESC, external.obs_datetime DESC;").each do |record|
|
|
114
|
+
to_remove << record['patient_id'].to_i
|
|
115
|
+
end
|
|
116
|
+
to_remove.join(',')
|
|
117
|
+
end
|
|
118
|
+
# rubocop:enable Metrics/MethodLength
|
|
119
|
+
# rubocop:enable Metrics/AbcSize
|
|
120
|
+
|
|
121
|
+
def rifapentine_concept
|
|
122
|
+
@rifapentine_concept ||= ::ConceptName.find_by!(name: 'Rifapentine')
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def isoniazid_rifapentine_concept
|
|
126
|
+
@isoniazid_rifapentine_concept ||= ::ConceptName.find_by!(name: 'Isoniazid/Rifapentine')
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def patient_on_3hp?(patient)
|
|
130
|
+
drug_concepts = patient['drug_concepts'].split(',').collect(&:to_i)
|
|
131
|
+
drug_concepts.intersect?([rifapentine_concept.concept_id, isoniazid_rifapentine_concept&.concept_id])
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def patient_on_tb_treatment?(patient_id)
|
|
135
|
+
::Observation.where(person_id: patient_id, concept_id: ::ConceptName.find_by_name('TB status').concept_id,
|
|
136
|
+
value_coded: ::ConceptName.find_by_name('Confirmed TB on treatment').concept_id)
|
|
137
|
+
.where("obs_datetime < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}").exists?
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|