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,414 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MalawiHivProgramReports
|
|
4
|
+
module Pepfar
|
|
5
|
+
class ViralLoadCoverage
|
|
6
|
+
attr_reader :start_date, :end_date
|
|
7
|
+
|
|
8
|
+
include Utils
|
|
9
|
+
|
|
10
|
+
def initialize(**params)
|
|
11
|
+
@start_date = params[:start_date]&.to_date
|
|
12
|
+
raise ::InvalidParameterError, 'start_date is required' unless @start_date
|
|
13
|
+
|
|
14
|
+
@end_date = params[:end_date]&.to_date || (@start_date + 12.months)
|
|
15
|
+
raise ::InvalidParameterError, "start_date can't be greater than end_date" if @start_date > @end_date
|
|
16
|
+
|
|
17
|
+
@tx_curr_definition = params.fetch(:tx_curr_definition, 'pepfar')&.downcase
|
|
18
|
+
unless %w[moh pepfar].include?(@tx_curr_definition)
|
|
19
|
+
raise ::InvalidParameterError, "tx_curr_definition can only moh or pepfar not #{@tx_curr_definition}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
@rebuild_outcomes = params.fetch(:rebuild_outcomes, 'true')&.casecmp?('true')
|
|
23
|
+
@type = params.fetch(:application, 'poc')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def find_report
|
|
27
|
+
report = init_report
|
|
28
|
+
|
|
29
|
+
case @type
|
|
30
|
+
when /poc/i then build_poc_report(report)
|
|
31
|
+
when /emastercard/i then build_emastercard_report(report)
|
|
32
|
+
else raise ::InvalidParameterError, "Report type must be one of [poc, emastercard] not #{@type}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
report
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def vl_maternal_status(patient_list)
|
|
39
|
+
pregnant = pregnant_women(patient_list).map { |woman| woman['person_id'].to_i }
|
|
40
|
+
feeding = breast_feeding(patient_list - pregnant).map { |woman| woman['person_id'].to_i }
|
|
41
|
+
|
|
42
|
+
{
|
|
43
|
+
FP: pregnant,
|
|
44
|
+
FBf: feeding
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def pregnant_women(patient_list)
|
|
51
|
+
encounter_types = ::EncounterType.where(name: ['HIV CLINIC CONSULTATION', 'HIV STAGING'])
|
|
52
|
+
.select(:encounter_type_id)
|
|
53
|
+
|
|
54
|
+
pregnant_concepts = ::ConceptName.where(name: ['Is patient pregnant?', 'patient pregnant'])
|
|
55
|
+
.select(:concept_id)
|
|
56
|
+
|
|
57
|
+
ActiveRecord::Base.connection.select_all <<~SQL
|
|
58
|
+
SELECT obs.person_id,obs.value_coded
|
|
59
|
+
FROM obs obs
|
|
60
|
+
INNER JOIN encounter enc
|
|
61
|
+
ON enc.encounter_id = obs.encounter_id
|
|
62
|
+
AND enc.voided = 0
|
|
63
|
+
AND enc.encounter_type IN (#{encounter_types.to_sql})
|
|
64
|
+
INNER JOIN temp_earliest_start_date e
|
|
65
|
+
ON e.patient_id = enc.patient_id
|
|
66
|
+
AND LEFT(e.gender, 1) = 'F'
|
|
67
|
+
INNER JOIN temp_patient_outcomes
|
|
68
|
+
ON temp_patient_outcomes.patient_id = e.patient_id
|
|
69
|
+
AND temp_patient_outcomes.cum_outcome = 'On antiretrovirals'
|
|
70
|
+
INNER JOIN (
|
|
71
|
+
SELECT person_id, MAX(obs_datetime) AS obs_datetime
|
|
72
|
+
FROM obs
|
|
73
|
+
INNER JOIN encounter
|
|
74
|
+
ON encounter.encounter_id = obs.encounter_id
|
|
75
|
+
AND encounter.encounter_type IN (#{encounter_types.to_sql})
|
|
76
|
+
AND encounter.voided = 0
|
|
77
|
+
WHERE concept_id IN (#{pregnant_concepts.to_sql})
|
|
78
|
+
AND obs_datetime BETWEEN DATE('#{@start_date}') AND DATE('#{@end_date}') + INTERVAL 1 DAY
|
|
79
|
+
AND obs.voided = 0
|
|
80
|
+
GROUP BY person_id
|
|
81
|
+
) AS max_obs
|
|
82
|
+
ON max_obs.person_id = obs.person_id
|
|
83
|
+
AND max_obs.obs_datetime = obs.obs_datetime
|
|
84
|
+
WHERE obs.concept_id IN (#{pregnant_concepts.to_sql})
|
|
85
|
+
AND obs.voided = 0
|
|
86
|
+
AND obs.person_id IN (#{patient_list.join(',')})
|
|
87
|
+
GROUP BY obs.person_id
|
|
88
|
+
HAVING obs.value_coded = 1065
|
|
89
|
+
ORDER BY obs.obs_datetime DESC;
|
|
90
|
+
SQL
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def breast_feeding(patient_list)
|
|
94
|
+
encounter_types = ::EncounterType.where(name: ['HIV CLINIC CONSULTATION', 'HIV STAGING'])
|
|
95
|
+
.select(:encounter_type_id)
|
|
96
|
+
|
|
97
|
+
breastfeeding_concepts = ::ConceptName.where(name: ['Breast feeding?', 'Breast feeding', 'Breastfeeding'])
|
|
98
|
+
.select(:concept_id)
|
|
99
|
+
|
|
100
|
+
ActiveRecord::Base.connection.select_all <<~SQL
|
|
101
|
+
SELECT obs.person_id,obs.value_coded
|
|
102
|
+
FROM obs
|
|
103
|
+
INNER JOIN encounter enc
|
|
104
|
+
ON enc.encounter_id = obs.encounter_id
|
|
105
|
+
AND enc.voided = 0
|
|
106
|
+
AND enc.encounter_type IN (#{encounter_types.to_sql})
|
|
107
|
+
INNER JOIN temp_earliest_start_date e
|
|
108
|
+
ON e.patient_id = enc.patient_id
|
|
109
|
+
AND LEFT(e.gender, 1) = 'F'
|
|
110
|
+
INNER JOIN temp_patient_outcomes
|
|
111
|
+
ON temp_patient_outcomes.patient_id = e.patient_id
|
|
112
|
+
AND temp_patient_outcomes.cum_outcome = 'On antiretrovirals'
|
|
113
|
+
INNER JOIN (
|
|
114
|
+
SELECT person_id, MAX(obs_datetime) AS obs_datetime
|
|
115
|
+
FROM obs
|
|
116
|
+
INNER JOIN encounter
|
|
117
|
+
ON encounter.encounter_id = obs.encounter_id
|
|
118
|
+
AND encounter.encounter_type IN (#{encounter_types.to_sql})
|
|
119
|
+
AND encounter.voided = 0
|
|
120
|
+
WHERE person_id IN (SELECT patient_id FROM temp_patient_outcomes WHERE cum_outcome = 'On antiretrovirals')
|
|
121
|
+
AND concept_id IN (#{breastfeeding_concepts.to_sql})
|
|
122
|
+
AND obs.voided = 0
|
|
123
|
+
AND obs_datetime < DATE('#{end_date}') + INTERVAL 1 DAY
|
|
124
|
+
GROUP BY person_id
|
|
125
|
+
) AS max_obs
|
|
126
|
+
ON max_obs.person_id = obs.person_id
|
|
127
|
+
AND max_obs.obs_datetime = obs.obs_datetime
|
|
128
|
+
WHERE obs.person_id = e.patient_id
|
|
129
|
+
AND obs.person_id IN (#{patient_list.join(',')})
|
|
130
|
+
AND obs.obs_datetime BETWEEN DATE("#{@start_date}") AND DATE("#{@end_date}") + INTERVAL 1 DAY
|
|
131
|
+
AND obs.concept_id IN (#{breastfeeding_concepts.to_sql})
|
|
132
|
+
AND obs.voided = 0
|
|
133
|
+
GROUP BY obs.person_id
|
|
134
|
+
HAVING obs.value_coded = 1065
|
|
135
|
+
ORDER BY obs.obs_datetime DESC;
|
|
136
|
+
SQL
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def build_poc_report(report)
|
|
140
|
+
find_patients_alive_and_on_art.each { |patient| report[patient['age_group']][:tx_curr] << patient }
|
|
141
|
+
find_patients_due_for_initial_viral_load.each do |patient|
|
|
142
|
+
report[patient['age_group']][:due_for_vl] << patient
|
|
143
|
+
end
|
|
144
|
+
find_patients_with_overdue_viral_load.each { |patient| report[patient['age_group']][:due_for_vl] << patient }
|
|
145
|
+
load_patient_tests_into_report(report)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def build_emastercard_report(report)
|
|
149
|
+
find_patients_alive_and_on_art.each { |patient| report[patient['age_group']][:tx_curr] << patient }
|
|
150
|
+
load_emastercard_results_into_report(report)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def init_report
|
|
154
|
+
pepfar_age_groups.each_with_object({}) do |age_group, report|
|
|
155
|
+
report[age_group] = {
|
|
156
|
+
tx_curr: [],
|
|
157
|
+
due_for_vl: [],
|
|
158
|
+
drawn: { routine: [], targeted: [] },
|
|
159
|
+
high_vl: { routine: [], targeted: [] },
|
|
160
|
+
low_vl: { routine: [], targeted: [] }
|
|
161
|
+
}
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def load_patient_tests_into_report(report)
|
|
166
|
+
find_patients_with_viral_load.each do |patient|
|
|
167
|
+
age_group = patient['age_group']
|
|
168
|
+
reason_for_test = (patient['reason_for_test'] || 'Routine').match?(/Routine/i) ? :routine : :targeted
|
|
169
|
+
|
|
170
|
+
report[age_group][:drawn][reason_for_test] << patient
|
|
171
|
+
next unless patient['result_value']
|
|
172
|
+
|
|
173
|
+
if patient['result_value'].casecmp?('LDL')
|
|
174
|
+
report[age_group][:low_vl][reason_for_test] << patient
|
|
175
|
+
elsif patient['result_value'].to_i < 1000
|
|
176
|
+
report[age_group][:low_vl][reason_for_test] << patient
|
|
177
|
+
else
|
|
178
|
+
report[age_group][:high_vl][reason_for_test] << patient
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def load_emastercard_results_into_report(report)
|
|
184
|
+
find_emastercard_patient_results.each do |patient|
|
|
185
|
+
if patient['result_value'] < 1000
|
|
186
|
+
report[patient['age_group']][:low_vl][:routine] << patient
|
|
187
|
+
else
|
|
188
|
+
report[patient['age_group']][:high_vl][:routine] << patient
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def find_patients_alive_and_on_art
|
|
194
|
+
patients = MalawiHivProgramReports::Clinic::PatientsAliveAndOnTreatment
|
|
195
|
+
.new(start_date:, end_date:, outcomes_definition: @tx_curr_definition, rebuild_outcomes: @rebuild_outcomes)
|
|
196
|
+
.query
|
|
197
|
+
pepfar_patient_drilldown_information(patients, end_date).map do |patient|
|
|
198
|
+
{
|
|
199
|
+
'patient_id' => patient.patient_id,
|
|
200
|
+
'arv_number' => patient.arv_number,
|
|
201
|
+
'age_group' => patient.age_group,
|
|
202
|
+
'birthdate' => patient.birthdate,
|
|
203
|
+
'gender' => patient.gender
|
|
204
|
+
}
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
##
|
|
209
|
+
# Selects patients whose last viral load should have expired before the end of the reporting period.
|
|
210
|
+
#
|
|
211
|
+
# Patients returned by this aren't necessarily due for viral load, they may have
|
|
212
|
+
# their current milestone delayed. So extra processing on the patients is required
|
|
213
|
+
# to filter out the patients with delayed milestones.
|
|
214
|
+
def find_patients_with_overdue_viral_load
|
|
215
|
+
# Find all patients whose last order's expires in or before the reporting period (making them due)
|
|
216
|
+
# or patients whose first order comes at 6 months or greater after starting ART.
|
|
217
|
+
ActiveRecord::Base.connection.select_all <<~SQL
|
|
218
|
+
SELECT orders.patient_id,
|
|
219
|
+
disaggregated_age_group(patient.birthdate,
|
|
220
|
+
DATE(#{ActiveRecord::Base.connection.quote(end_date)})) AS age_group,
|
|
221
|
+
patient.birthdate,
|
|
222
|
+
patient.gender,
|
|
223
|
+
patient_identifier.identifier AS arv_number
|
|
224
|
+
FROM orders
|
|
225
|
+
INNER JOIN order_type
|
|
226
|
+
ON order_type.order_type_id = orders.order_type_id
|
|
227
|
+
AND order_type.name = 'Lab'
|
|
228
|
+
AND order_type.retired = 0
|
|
229
|
+
INNER JOIN concept_name
|
|
230
|
+
ON concept_name.concept_id = orders.concept_id
|
|
231
|
+
AND concept_name.name IN ('Blood', 'DBS (Free drop to DBS card)', 'DBS (Using capillary tube)')
|
|
232
|
+
AND concept_name.voided = 0
|
|
233
|
+
INNER JOIN (
|
|
234
|
+
/* Get the latest order dates for each patient */
|
|
235
|
+
SELECT orders.patient_id, MAX(orders.start_date) AS start_date
|
|
236
|
+
FROM orders
|
|
237
|
+
INNER JOIN order_type
|
|
238
|
+
ON order_type.order_type_id = orders.order_type_id
|
|
239
|
+
AND order_type.name = 'Lab'
|
|
240
|
+
AND order_type.retired = 0
|
|
241
|
+
INNER JOIN concept_name
|
|
242
|
+
ON concept_name.concept_id = orders.concept_id
|
|
243
|
+
AND concept_name.name IN ('Blood', 'DBS (Free drop to DBS card)', 'DBS (Using capillary tube)')
|
|
244
|
+
AND concept_name.voided = 0
|
|
245
|
+
WHERE orders.start_date <= DATE(#{ActiveRecord::Base.connection.quote(end_date)}) - INTERVAL 12 MONTH
|
|
246
|
+
AND orders.voided = 0
|
|
247
|
+
GROUP BY orders.patient_id
|
|
248
|
+
) AS latest_patient_order_date
|
|
249
|
+
ON latest_patient_order_date.patient_id = orders.patient_id
|
|
250
|
+
AND latest_patient_order_date.start_date = orders.start_date
|
|
251
|
+
INNER JOIN temp_earliest_start_date AS patient ON patient.patient_id = orders.patient_id
|
|
252
|
+
INNER JOIN temp_patient_outcomes AS outcomes
|
|
253
|
+
ON outcomes.patient_id = patient.patient_id
|
|
254
|
+
AND outcomes.cum_outcome = 'On antiretrovirals'
|
|
255
|
+
LEFT JOIN patient_identifier
|
|
256
|
+
ON patient_identifier.patient_id = orders.patient_id
|
|
257
|
+
AND patient_identifier.identifier_type IN (#{pepfar_patient_identifier_type.to_sql})
|
|
258
|
+
AND patient_identifier.voided = 0
|
|
259
|
+
WHERE orders.start_date < DATE(#{ActiveRecord::Base.connection.quote(end_date)}) - INTERVAL 12 MONTH
|
|
260
|
+
GROUP BY orders.patient_id
|
|
261
|
+
SQL
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
##
|
|
265
|
+
# Returns all patients that have been on ART for at least 6 months and have never had a Viral Load.
|
|
266
|
+
def find_patients_due_for_initial_viral_load
|
|
267
|
+
ActiveRecord::Base.connection.select_all <<~SQL
|
|
268
|
+
SELECT patient.patient_id,
|
|
269
|
+
disaggregated_age_group(patient.birthdate,
|
|
270
|
+
DATE(#{ActiveRecord::Base.connection.quote(end_date)})) AS age_group,
|
|
271
|
+
patient.birthdate,
|
|
272
|
+
patient.gender,
|
|
273
|
+
patient_identifier.identifier AS arv_number
|
|
274
|
+
FROM temp_earliest_start_date AS patient
|
|
275
|
+
INNER JOIN temp_patient_outcomes AS outcomes
|
|
276
|
+
ON outcomes.patient_id = patient.patient_id
|
|
277
|
+
AND outcomes.cum_outcome = 'On antiretrovirals'
|
|
278
|
+
INNER JOIN patient_identifier
|
|
279
|
+
ON patient_identifier.patient_id = patient.patient_id
|
|
280
|
+
AND patient_identifier.identifier_type IN (#{pepfar_patient_identifier_type.to_sql})
|
|
281
|
+
AND patient_identifier.voided = 0
|
|
282
|
+
WHERE patient.patient_id NOT IN (
|
|
283
|
+
SELECT DISTINCT orders.patient_id FROM orders
|
|
284
|
+
INNER JOIN order_type ON order_type.order_type_id = orders.order_type_id AND order_type.name = 'Lab'
|
|
285
|
+
INNER JOIN obs ON orders.order_id = obs.order_id AND obs.voided = 0
|
|
286
|
+
INNER JOIN concept_name ON concept_name.concept_id = obs.concept_id AND concept_name.name = 'Test type' AND concept_name.voided = 0
|
|
287
|
+
INNER JOIN concept_name AS test_name ON test_name.concept_id = obs.value_coded AND test_name.name = 'HIV Viral Load' AND test_name.voided = 0
|
|
288
|
+
WHERE orders.start_date <= DATE(#{ActiveRecord::Base.connection.quote(end_date)}) - INTERVAL 12 MONTH
|
|
289
|
+
AND orders.concept_id IN (SELECT concept_id FROM concept_name WHERE name IN ('Blood', 'DBS (Free drop to DBS card)', 'DBS (Using capillary tube)'))
|
|
290
|
+
AND orders.voided = 0
|
|
291
|
+
) AND patient.earliest_start_date <= DATE(#{ActiveRecord::Base.connection.quote(end_date)}) - INTERVAL 6 MONTH
|
|
292
|
+
GROUP BY patient.patient_id
|
|
293
|
+
SQL
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
##
|
|
297
|
+
# Find all patients that are on treatment with at least one VL before end of reporting period.
|
|
298
|
+
def find_patients_with_viral_load
|
|
299
|
+
ActiveRecord::Base.connection.select_all <<~SQL
|
|
300
|
+
SELECT orders.patient_id,
|
|
301
|
+
disaggregated_age_group(patient.birthdate,
|
|
302
|
+
DATE(#{ActiveRecord::Base.connection.quote(end_date)})) AS age_group,
|
|
303
|
+
patient.birthdate,
|
|
304
|
+
patient.gender,
|
|
305
|
+
patient_identifier.identifier AS arv_number,
|
|
306
|
+
orders.start_date AS order_date,
|
|
307
|
+
COALESCE(orders.discontinued_date, orders.start_date) AS sample_draw_date,
|
|
308
|
+
COALESCE(reason_for_test_value.name, reason_for_test.value_text) AS reason_for_test,
|
|
309
|
+
result.value_modifier AS result_modifier,
|
|
310
|
+
COALESCE(result.value_numeric, result.value_text) AS result_value
|
|
311
|
+
FROM orders
|
|
312
|
+
INNER JOIN order_type
|
|
313
|
+
ON order_type.order_type_id = orders.order_type_id
|
|
314
|
+
AND order_type.name = 'Lab'
|
|
315
|
+
AND order_type.retired = 0
|
|
316
|
+
INNER JOIN concept_name
|
|
317
|
+
ON concept_name.concept_id = orders.concept_id
|
|
318
|
+
AND concept_name.name IN ('Blood', 'DBS (Free drop to DBS card)', 'DBS (Using capillary tube)')
|
|
319
|
+
AND concept_name.voided = 0
|
|
320
|
+
LEFT JOIN obs AS reason_for_test
|
|
321
|
+
ON reason_for_test.order_id = orders.order_id
|
|
322
|
+
AND reason_for_test.concept_id IN (SELECT concept_id FROM concept_name WHERE name LIKE 'Reason for test' AND voided = 0)
|
|
323
|
+
AND reason_for_test.voided = 0
|
|
324
|
+
LEFT JOIN concept_name AS reason_for_test_value
|
|
325
|
+
ON reason_for_test_value.concept_id = reason_for_test.value_coded
|
|
326
|
+
AND reason_for_test_value.voided = 0
|
|
327
|
+
LEFT JOIN obs AS result
|
|
328
|
+
ON result.order_id = orders.order_id
|
|
329
|
+
AND result.concept_id IN (SELECT concept_id FROM concept_name WHERE name LIKE 'HIV Viral load' AND voided = 0)
|
|
330
|
+
AND result.voided = 0
|
|
331
|
+
AND (result.value_text IS NOT NULL OR result.value_numeric IS NOT NULL)
|
|
332
|
+
INNER JOIN (
|
|
333
|
+
/* Get the latest order dates for each patient */
|
|
334
|
+
SELECT orders.patient_id, MAX(orders.start_date) AS start_date
|
|
335
|
+
FROM orders
|
|
336
|
+
INNER JOIN order_type
|
|
337
|
+
ON order_type.order_type_id = orders.order_type_id
|
|
338
|
+
AND order_type.name = 'Lab'
|
|
339
|
+
AND order_type.retired = 0
|
|
340
|
+
INNER JOIN concept_name
|
|
341
|
+
ON concept_name.concept_id = orders.concept_id
|
|
342
|
+
AND concept_name.name IN ('Blood', 'DBS (Free drop to DBS card)', 'DBS (Using capillary tube)')
|
|
343
|
+
AND concept_name.voided = 0
|
|
344
|
+
WHERE orders.start_date < DATE(#{ActiveRecord::Base.connection.quote(end_date)}) + INTERVAL 1 DAY
|
|
345
|
+
AND orders.voided = 0
|
|
346
|
+
GROUP BY orders.patient_id
|
|
347
|
+
) AS latest_patient_order_date
|
|
348
|
+
ON latest_patient_order_date.patient_id = orders.patient_id
|
|
349
|
+
AND latest_patient_order_date.start_date = orders.start_date
|
|
350
|
+
INNER JOIN temp_earliest_start_date AS patient ON patient.patient_id = orders.patient_id
|
|
351
|
+
INNER JOIN temp_patient_outcomes AS outcomes
|
|
352
|
+
ON outcomes.patient_id = patient.patient_id
|
|
353
|
+
AND outcomes.cum_outcome = 'On antiretrovirals'
|
|
354
|
+
LEFT JOIN patient_identifier
|
|
355
|
+
ON patient_identifier.patient_id = orders.patient_id
|
|
356
|
+
AND patient_identifier.identifier_type IN (#{pepfar_patient_identifier_type.to_sql})
|
|
357
|
+
AND patient_identifier.voided = 0
|
|
358
|
+
WHERE orders.start_date < DATE(#{ActiveRecord::Base.connection.quote(end_date)}) + INTERVAL 1 DAY
|
|
359
|
+
AND orders.start_date >= DATE(#{ActiveRecord::Base.connection.quote(start_date)})
|
|
360
|
+
GROUP BY orders.patient_id
|
|
361
|
+
SQL
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
##
|
|
365
|
+
# Returns a Relation of all viral load tests.
|
|
366
|
+
def find_viral_load_tests
|
|
367
|
+
::Lab::LabTest.where(value_coded: concept('Viral load'))
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def find_emastercard_patient_results
|
|
371
|
+
ActiveRecord::Base.connection.select_all <<~SQL
|
|
372
|
+
SELECT obs.person_id AS patient_id,
|
|
373
|
+
patient_identifier.identifier AS arv_number,
|
|
374
|
+
patient.birthdate,
|
|
375
|
+
patient.gender,
|
|
376
|
+
disaggregated_age_group(patient.birthdate, #{ActiveRecord::Base.connection.quote(end_date)}) AS age_group,
|
|
377
|
+
obs.value_numeric AS result_value
|
|
378
|
+
FROM obs
|
|
379
|
+
INNER JOIN encounter ON encounter.encounter_id = obs.encounter_id AND encounter.voided = 0
|
|
380
|
+
INNER JOIN encounter_type ON encounter_type.encounter_type_id = encounter.encounter_type AND encounter_type.name = 'Lab'
|
|
381
|
+
INNER JOIN temp_earliest_start_date AS patient ON patient.patient_id = obs.person_id
|
|
382
|
+
INNER JOIN temp_patient_outcomes
|
|
383
|
+
ON temp_patient_outcomes.patient_id = obs.person_id
|
|
384
|
+
AND temp_patient_outcomes.cum_outcome = 'On antiretrovirals'
|
|
385
|
+
LEFT JOIN patient_identifier
|
|
386
|
+
ON patient_identifier.patient_id = obs.person_id
|
|
387
|
+
AND patient_identifier.voided = 0
|
|
388
|
+
AND patient_identifier.identifier_type IN (#{pepfar_patient_identifier_type.to_sql})
|
|
389
|
+
INNER JOIN (
|
|
390
|
+
SELECT obs.person_id, MAX(obs.obs_datetime) AS obs_datetime
|
|
391
|
+
FROM obs
|
|
392
|
+
INNER JOIN encounter ON encounter.encounter_id = obs.encounter_id AND encounter.voided = 0
|
|
393
|
+
INNER JOIN encounter_type ON encounter_type.encounter_type_id = encounter.encounter_type AND encounter_type.name = 'Lab'
|
|
394
|
+
WHERE obs.concept_id IN (#{concept('Viral load').to_sql})
|
|
395
|
+
AND obs.obs_datetime > DATE(#{ActiveRecord::Base.connection.quote(start_date)}) - INTERVAL 1 DAY
|
|
396
|
+
AND obs.obs_datetime < DATE(#{ActiveRecord::Base.connection.quote(end_date)}) + INTERVAL 1 DAY
|
|
397
|
+
AND obs.voided = 0
|
|
398
|
+
GROUP BY obs.person_id
|
|
399
|
+
) AS latest_results
|
|
400
|
+
ON latest_results.person_id = obs.person_id
|
|
401
|
+
AND latest_results.obs_datetime = obs.obs_datetime
|
|
402
|
+
WHERE obs.concept_id IN (#{concept('Viral load').to_sql})
|
|
403
|
+
AND obs.value_numeric IS NOT NULL
|
|
404
|
+
AND obs.voided = 0
|
|
405
|
+
GROUP BY obs.person_id
|
|
406
|
+
SQL
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def concept(name)
|
|
410
|
+
::ConceptName.where(name:).select(:concept_id)
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
end
|