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.
- 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 +2337 -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 +205 -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,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Report for Showing all HIV Viral Load Tests Done Or Sample Collected per the specified period
|
4
|
+
module MalawiHivProgramReports
|
5
|
+
module Clinic
|
6
|
+
class VlCollection
|
7
|
+
include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
|
8
|
+
def initialize(start_date:, end_date:, **kwargs)
|
9
|
+
@start_date = start_date.to_date.beginning_of_day
|
10
|
+
@end_date = end_date.to_date.end_of_day
|
11
|
+
@occupation = kwargs[:occupation]
|
12
|
+
end
|
13
|
+
|
14
|
+
def find_report
|
15
|
+
data
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def data
|
21
|
+
ActiveRecord::Base.connection.select_all <<~SQL
|
22
|
+
SELECT
|
23
|
+
o.person_id AS patient_id,
|
24
|
+
pn.given_name,
|
25
|
+
pn.family_name,
|
26
|
+
p.gender,
|
27
|
+
p.birthdate,
|
28
|
+
i.identifier AS identifier,
|
29
|
+
DATE(ord.start_date) AS order_date
|
30
|
+
FROM obs o
|
31
|
+
INNER JOIN orders ord ON ord.order_id = o.order_id AND ord.voided = 0
|
32
|
+
INNER JOIN person p ON p.person_id = o.person_id AND p.voided = 0
|
33
|
+
INNER JOIN person_name pn ON pn.person_id = p.person_id AND pn.voided = 0
|
34
|
+
LEFT JOIN patient_identifier i ON i.patient_id = p.person_id AND i.voided = 0 AND i.identifier_type = #{indetifier_type}
|
35
|
+
LEFT JOIN (#{current_occupation_query}) AS a ON a.person_id = p.person_id
|
36
|
+
WHERE o.concept_id = 9737 -- Test Type
|
37
|
+
AND o.value_coded = 856 -- Viral Load
|
38
|
+
AND o.obs_datetime BETWEEN '#{@start_date}' AND '#{@end_date}'
|
39
|
+
AND o.voided = 0 #{%w[Military Civilian].include?(@occupation) ? 'AND' : ''} #{occupation_filter(occupation: @occupation, field_name: 'value', table_name: 'a', include_clause: false)}
|
40
|
+
SQL
|
41
|
+
end
|
42
|
+
|
43
|
+
def indetifier_type
|
44
|
+
@indetifier_type ||= ::PatientIdentifierType.find_by_name!(::GlobalPropertyService.use_filing_numbers? ? 'Filing Number' : 'ARV Number').id
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,338 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MalawiHivProgramReports
|
4
|
+
module Cohort
|
5
|
+
class Outcomes
|
6
|
+
include MalawiHivProgramReports::Adapters::Moh::Custom
|
7
|
+
include MalawiHivProgramReports::Utils::ModelUtils
|
8
|
+
attr_reader :end_date
|
9
|
+
|
10
|
+
def initialize(end_date:, definition: 'moh', location: nil)
|
11
|
+
definition = definition.downcase
|
12
|
+
raise ::ArgumentError, "Invalid outcomes definition: #{definition}" unless %w[moh pepfar].include?(definition)
|
13
|
+
|
14
|
+
@adapter = ActiveRecord::Base.connection.adapter_name.downcase
|
15
|
+
@end_date = end_date.to_date
|
16
|
+
@definition = definition
|
17
|
+
@location = location
|
18
|
+
end
|
19
|
+
|
20
|
+
def update_cummulative_outcomes
|
21
|
+
initialize_table
|
22
|
+
|
23
|
+
# HIC SUNT DRACONIS: The order of the operations below matters,
|
24
|
+
# do not change it unless you know what you are doing!!!
|
25
|
+
load_patients_who_died
|
26
|
+
load_patients_who_stopped_treatment
|
27
|
+
load_patients_on_pre_art
|
28
|
+
load_patients_without_state
|
29
|
+
load_patients_without_drug_orders
|
30
|
+
load_patients_on_treatment
|
31
|
+
load_defaulters
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def arv_drugs_concept_set
|
37
|
+
@arv_drugs_concept_set ||= ::ConceptSet.where(set: concept_name('Antiretroviral drugs').concept)
|
38
|
+
.select(:concept_id)
|
39
|
+
end
|
40
|
+
|
41
|
+
def drug_order_type
|
42
|
+
@drug_order_type ||= order_type('Drug order')
|
43
|
+
end
|
44
|
+
|
45
|
+
def program_states(*names)
|
46
|
+
::ProgramWorkflowState.joins(:program_workflow)
|
47
|
+
.joins(:concept)
|
48
|
+
.merge(::ProgramWorkflow.where(program: hiv_program))
|
49
|
+
.merge(::Concept.joins(:concept_names)
|
50
|
+
.merge(::ConceptName.where(name: names)))
|
51
|
+
.select(:program_workflow_state_id)
|
52
|
+
end
|
53
|
+
|
54
|
+
def hiv_program
|
55
|
+
@hiv_program ||= program('HIV Program')
|
56
|
+
end
|
57
|
+
|
58
|
+
def initialize_table
|
59
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
60
|
+
DROP TABLE IF EXISTS temp_patient_outcomes
|
61
|
+
SQL
|
62
|
+
|
63
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
64
|
+
CREATE TABLE temp_patient_outcomes (
|
65
|
+
patient_id INT NOT NULL,
|
66
|
+
cum_outcome VARCHAR(120) NOT NULL,
|
67
|
+
outcome_date DATE DEFAULT NULL,
|
68
|
+
site_id INT NOT NULL,
|
69
|
+
PRIMARY KEY (patient_id, site_id)
|
70
|
+
)
|
71
|
+
SQL
|
72
|
+
|
73
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
74
|
+
CREATE INDEX tpo_outcomes ON temp_patient_outcomes (patient_id, cum_outcome, outcome_date, site_id)
|
75
|
+
SQL
|
76
|
+
end
|
77
|
+
|
78
|
+
# Loads all patiens with an outcome of died as of given date
|
79
|
+
# into the temp_patient_outcomes table.
|
80
|
+
def load_patients_who_died
|
81
|
+
date = ActiveRecord::Base.connection.quote(end_date)
|
82
|
+
|
83
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
84
|
+
INSERT INTO temp_patient_outcomes
|
85
|
+
SELECT patients.patient_id, 'Patient died', MIN(patient_state.start_date), #{@location}
|
86
|
+
FROM temp_earliest_start_date AS patients
|
87
|
+
INNER JOIN patient_program
|
88
|
+
ON patient_program.patient_id = patients.patient_id
|
89
|
+
AND patient_program.program_id = #{hiv_program.program_id}
|
90
|
+
AND patient_program.voided = 0 #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
|
91
|
+
INNER JOIN patient_state
|
92
|
+
ON patient_state.patient_program_id = patient_program.patient_program_id
|
93
|
+
AND patient_state.state = (#{program_states('Patient died').limit(1).to_sql})
|
94
|
+
AND patient_state.start_date < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
|
95
|
+
AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
|
96
|
+
WHERE patients.date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
|
97
|
+
GROUP BY patients.patient_id
|
98
|
+
SQL
|
99
|
+
end
|
100
|
+
|
101
|
+
# Loads all patients with an outcome of transferred out or
|
102
|
+
# treatment stopped into temp_patient_outcomes table.
|
103
|
+
def load_patients_who_stopped_treatment
|
104
|
+
date = ActiveRecord::Base.connection.quote(end_date)
|
105
|
+
|
106
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
107
|
+
INSERT INTO temp_patient_outcomes
|
108
|
+
SELECT patients.patient_id,
|
109
|
+
(
|
110
|
+
SELECT name FROM concept_name
|
111
|
+
WHERE concept_id = (
|
112
|
+
SELECT concept_id FROM program_workflow_state
|
113
|
+
WHERE program_workflow_state_id = patient_state.state
|
114
|
+
LIMIT 1
|
115
|
+
)
|
116
|
+
) AS cum_outcome,
|
117
|
+
patient_state.start_date, #{@location}
|
118
|
+
FROM temp_earliest_start_date AS patients
|
119
|
+
INNER JOIN patient_program
|
120
|
+
ON patient_program.patient_id = patients.patient_id
|
121
|
+
AND patient_program.program_id = #{hiv_program.program_id}
|
122
|
+
AND patient_program.voided = 0 #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
|
123
|
+
INNER JOIN patient_state
|
124
|
+
ON patient_state.patient_program_id = patient_program.patient_program_id
|
125
|
+
AND patient_state.state IN (#{program_states('Patient transferred out', 'Treatment stopped').to_sql})
|
126
|
+
AND patient_state.start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
|
127
|
+
AND (patient_state.end_date >= #{date} OR patient_state.end_date IS NULL)
|
128
|
+
AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
|
129
|
+
INNER JOIN (
|
130
|
+
SELECT patient_program_id, MAX(start_date) AS start_date
|
131
|
+
FROM patient_state
|
132
|
+
WHERE patient_state.start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
|
133
|
+
AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
|
134
|
+
GROUP BY patient_program_id
|
135
|
+
) AS max_patient_state
|
136
|
+
ON max_patient_state.patient_program_id = patient_state.patient_program_id
|
137
|
+
AND max_patient_state.start_date = patient_state.start_date #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
|
138
|
+
WHERE patients.date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
|
139
|
+
AND patients.patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
|
140
|
+
GROUP BY patients.patient_id #{@adapter == 'mysql2' ? '' : ',patient_state.state, patient_state.start_date'}
|
141
|
+
SQL
|
142
|
+
end
|
143
|
+
|
144
|
+
# Load all patients on Pre-ART.
|
145
|
+
def load_patients_on_pre_art
|
146
|
+
date = ActiveRecord::Base.connection.quote(end_date)
|
147
|
+
|
148
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
149
|
+
INSERT INTO temp_patient_outcomes
|
150
|
+
SELECT patients.patient_id,
|
151
|
+
CASE
|
152
|
+
WHEN #{current_defaulter_function('patients.patient_id')} = 1 THEN 'Defaulted'
|
153
|
+
ELSE 'Pre-ART (Continue)'
|
154
|
+
END AS cum_outcome,
|
155
|
+
patient_state.start_date, #{@location}
|
156
|
+
FROM temp_earliest_start_date AS patients
|
157
|
+
INNER JOIN patient_program
|
158
|
+
ON patient_program.patient_id = patients.patient_id
|
159
|
+
AND patient_program.program_id = #{hiv_program.program_id}
|
160
|
+
AND patient_program.voided = 0 #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
|
161
|
+
INNER JOIN patient_state
|
162
|
+
ON patient_state.patient_program_id = patient_program.patient_program_id
|
163
|
+
AND patient_state.state = (#{program_states('Pre-ART (Continue)').limit(1).to_sql})
|
164
|
+
AND patient_state.start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
|
165
|
+
AND (patient_state.end_date >= #{date} OR patient_state.end_date IS NULL)
|
166
|
+
AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
|
167
|
+
INNER JOIN (
|
168
|
+
SELECT patient_program_id, MAX(start_date) AS start_date
|
169
|
+
FROM patient_state
|
170
|
+
WHERE patient_state.start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
|
171
|
+
AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
|
172
|
+
GROUP BY patient_program_id
|
173
|
+
) AS max_patient_state
|
174
|
+
ON max_patient_state.patient_program_id = patient_state.patient_program_id
|
175
|
+
AND max_patient_state.start_date = patient_state.start_date #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
|
176
|
+
WHERE patients.date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
|
177
|
+
AND patients.patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
|
178
|
+
GROUP BY patients.patient_id #{@adapter == 'mysql2' ? '' : ',patient_state.start_date'}
|
179
|
+
SQL
|
180
|
+
end
|
181
|
+
|
182
|
+
# Load all patients without a state
|
183
|
+
def load_patients_without_state
|
184
|
+
date = ActiveRecord::Base.connection.quote(end_date)
|
185
|
+
|
186
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
187
|
+
INSERT INTO temp_patient_outcomes
|
188
|
+
SELECT patients.patient_id,
|
189
|
+
CASE
|
190
|
+
WHEN #{current_defaulter_function('patients.patient_id')} = 1 THEN 'Defaulted'
|
191
|
+
ELSE 'Unknown'
|
192
|
+
END AS cum_outcome,
|
193
|
+
NULL, #{@location} as site_id
|
194
|
+
FROM temp_earliest_start_date AS patients
|
195
|
+
INNER JOIN patient_program
|
196
|
+
ON patient_program.patient_id = patients.patient_id
|
197
|
+
AND patient_program.program_id = #{hiv_program.program_id}
|
198
|
+
AND patient_program.voided = 0 #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
|
199
|
+
WHERE patients.date_enrolled <= #{date}
|
200
|
+
AND patient_program.patient_program_id NOT IN (
|
201
|
+
SELECT patient_program_id
|
202
|
+
FROM patient_state
|
203
|
+
WHERE start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
|
204
|
+
AND voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
|
205
|
+
)
|
206
|
+
AND patients.patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'patient_program.site_id', location: @location)} )
|
207
|
+
GROUP BY patients.patient_id
|
208
|
+
SQL
|
209
|
+
end
|
210
|
+
|
211
|
+
# Load all patients without drug orders or have drug orders
|
212
|
+
# without a quantity.
|
213
|
+
def load_patients_without_drug_orders
|
214
|
+
date = ActiveRecord::Base.connection.quote(end_date)
|
215
|
+
|
216
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
217
|
+
INSERT INTO temp_patient_outcomes
|
218
|
+
SELECT patients.patient_id,
|
219
|
+
'Unknown',
|
220
|
+
NULL, #{@location}
|
221
|
+
FROM temp_earliest_start_date AS patients
|
222
|
+
WHERE date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
|
223
|
+
AND patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
|
224
|
+
AND patient_id NOT IN (
|
225
|
+
SELECT patient_id
|
226
|
+
FROM orders
|
227
|
+
LEFT JOIN drug_order ON orders.order_id = drug_order.order_id #{site_manager(operator: 'AND', column: 'drug_order.site_id', location: @location)}
|
228
|
+
WHERE start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
|
229
|
+
AND quantity > 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
|
230
|
+
AND order_type_id = #{drug_order_type.order_type_id}
|
231
|
+
AND concept_id IN (#{arv_drugs_concept_set.to_sql})
|
232
|
+
)
|
233
|
+
SQL
|
234
|
+
end
|
235
|
+
|
236
|
+
# Loads all patients who are on treatment
|
237
|
+
def load_patients_on_treatment
|
238
|
+
date = ActiveRecord::Base.connection.quote(end_date)
|
239
|
+
|
240
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
241
|
+
INSERT INTO temp_patient_outcomes
|
242
|
+
SELECT patients.patient_id, 'On antiretrovirals', patient_state.start_date, #{@location}
|
243
|
+
FROM temp_earliest_start_date AS patients
|
244
|
+
INNER JOIN patient_program
|
245
|
+
ON patient_program.patient_id = patients.patient_id
|
246
|
+
AND patient_program.program_id = #{hiv_program.program_id}
|
247
|
+
AND patient_program.voided = 0 #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
|
248
|
+
/* Get patients' `on ARV` states that are before given date */
|
249
|
+
INNER JOIN patient_state
|
250
|
+
ON patient_state.patient_program_id = patient_program.patient_program_id
|
251
|
+
AND patient_state.state = (#{program_states('On antiretrovirals').limit(1).to_sql})
|
252
|
+
AND patient_state.start_date < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
|
253
|
+
AND (patient_state.end_date >= #{date} OR patient_state.end_date IS NULL)
|
254
|
+
AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
|
255
|
+
/* Select only the most recent state out of those retrieved above */
|
256
|
+
INNER JOIN (
|
257
|
+
SELECT patient_program_id, MAX(start_date) AS start_date
|
258
|
+
FROM patient_state
|
259
|
+
WHERE patient_state.start_date < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
|
260
|
+
AND patient_state.voided = 0 #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
|
261
|
+
GROUP BY patient_program_id
|
262
|
+
) AS max_patient_state
|
263
|
+
ON max_patient_state.patient_program_id = patient_state.patient_program_id
|
264
|
+
AND max_patient_state.start_date = patient_state.start_date #{site_manager(operator: 'AND', column: 'patient_state.site_id', location: @location)}
|
265
|
+
/* HACK: Ensure that the states captured above do correspond have corresponding
|
266
|
+
ARV dispensations. In other words filter out any `on ARVs` states whose
|
267
|
+
dispensation's may have been voided or states that were created manually
|
268
|
+
without any drugs being dispensed. */
|
269
|
+
INNER JOIN (
|
270
|
+
SELECT orders.patient_id, MIN(orders.auto_expire_date) AS auto_expire_date
|
271
|
+
FROM orders
|
272
|
+
INNER JOIN drug_order ON orders.order_id = drug_order.order_id #{site_manager(operator: 'AND', column: 'drug_order.site_id', location: @location)}
|
273
|
+
INNER JOIN (
|
274
|
+
SELECT patient_id, MAX(start_date) AS start_date
|
275
|
+
FROM orders
|
276
|
+
INNER JOIN drug_order ON orders.order_id = drug_order.order_id #{site_manager(operator: 'AND', column: 'drug_order.site_id', location: @location)} AND drug_order.quantity > 0
|
277
|
+
WHERE order_type_id = #{drug_order_type.order_type_id}
|
278
|
+
AND concept_id IN (#{arv_drugs_concept_set.to_sql})
|
279
|
+
AND orders.start_date < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
|
280
|
+
AND voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
|
281
|
+
AND patient_id IN (SELECT patient_id FROM temp_earliest_start_date #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
|
282
|
+
AND patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
|
283
|
+
GROUP BY patient_id
|
284
|
+
) AS max_drug_orders
|
285
|
+
ON max_drug_orders.patient_id = orders.patient_id
|
286
|
+
AND max_drug_orders.start_date = orders.start_date #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
|
287
|
+
WHERE order_type_id = #{drug_order_type.order_type_id}
|
288
|
+
AND concept_id IN (#{arv_drugs_concept_set.to_sql})
|
289
|
+
AND orders.start_date < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
|
290
|
+
AND quantity > 0
|
291
|
+
AND voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
|
292
|
+
AND orders.patient_id IN (SELECT patient_id FROM temp_earliest_start_date #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)})
|
293
|
+
AND orders.patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)})
|
294
|
+
GROUP BY orders.patient_id
|
295
|
+
) AS first_order_to_expire
|
296
|
+
ON (first_order_to_expire.auto_expire_date >= #{date} OR #{timestampdiff_manager(date1: 'DATE(first_order_to_expire.auto_expire_date)', date2: date, interval: 'DAY')} <= #{@definition == 'pepfar' ? 28 : 56})
|
297
|
+
AND first_order_to_expire.patient_id = patient_program.patient_id #{site_manager(operator: 'AND', column: 'patient_program.site_id', location: @location)}
|
298
|
+
WHERE patients.date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
|
299
|
+
AND patients.patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
|
300
|
+
GROUP BY patients.patient_id #{@adapter == 'mysql2' ? '' : ',patient_state.start_date'}
|
301
|
+
SQL
|
302
|
+
end
|
303
|
+
|
304
|
+
# Load defaulters
|
305
|
+
def load_defaulters
|
306
|
+
date = ActiveRecord::Base.connection.quote(end_date)
|
307
|
+
|
308
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
309
|
+
INSERT INTO temp_patient_outcomes
|
310
|
+
SELECT patient_id, #{patient_outcome_function('patient_id')}, NULL, #{@location}
|
311
|
+
FROM temp_earliest_start_date
|
312
|
+
WHERE date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
|
313
|
+
AND patient_id NOT IN (SELECT patient_id FROM temp_patient_outcomes #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)} )
|
314
|
+
SQL
|
315
|
+
end
|
316
|
+
|
317
|
+
def current_defaulter_function(sql_column)
|
318
|
+
defaulter_function = @definition == 'moh' ? 'current_defaulter' : 'current_pepfar_defaulter'
|
319
|
+
quoted_end_date = ActiveRecord::Base.connection.quote(end_date)
|
320
|
+
parameters = "#{sql_column}, #{quoted_end_date}, #{@location}"
|
321
|
+
function_manager(function: defaulter_function, location: @location, args: parameters)
|
322
|
+
rescue StandardError => e
|
323
|
+
Rails.logger.error "Error: #{e.message}"
|
324
|
+
raise "Invalid outcomes definition: #{@definition}"
|
325
|
+
end
|
326
|
+
|
327
|
+
def patient_outcome_function(sql_column)
|
328
|
+
outcome_function = @definition == 'moh' ? 'patient_outcome' : 'pepfar_patient_outcome'
|
329
|
+
quoted_end_date = ActiveRecord::Base.connection.quote(end_date)
|
330
|
+
parameters = "#{sql_column}, #{quoted_end_date}, #{@location}"
|
331
|
+
function_manager(function: outcome_function, location: @location, args: parameters)
|
332
|
+
rescue StandardError => e
|
333
|
+
Rails.logger.error "Error: #{e.message}"
|
334
|
+
raise "Invalid outcomes definition: #{@definition}"
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MalawiHivProgramReports
|
4
|
+
module Cohort
|
5
|
+
class Regimens
|
6
|
+
include MalawiHivProgramReports::Utils::ModelUtils
|
7
|
+
include MalawiHivProgramReports::Adapters::Moh::Custom
|
8
|
+
|
9
|
+
def patient_regimens(date, location)
|
10
|
+
@location = location
|
11
|
+
@adapter = ActiveRecord::Base.connection.adapter_name.downcase
|
12
|
+
date = ActiveRecord::Base.connection.quote(date)
|
13
|
+
|
14
|
+
ActiveRecord::Base.connection.select_all <<~SQL
|
15
|
+
SELECT prescriptions.patient_id,
|
16
|
+
regimens.name AS regimen_category,
|
17
|
+
prescriptions.drugs,
|
18
|
+
prescriptions.prescription_date
|
19
|
+
FROM (
|
20
|
+
SELECT orders.patient_id,
|
21
|
+
#{@adapter == 'mysql2' ? 'GROUP_CONCAT(DISTINCT drug_order.drug_inventory_id ORDER BY drug_order.drug_inventory_id ASC) AS drugs,' : "(SELECT STRING_AGG(drug_inventory_id::VARCHAR, ',') FROM (SELECT DISTINCT drug_order.drug_inventory_id FROM drug_order ORDER BY drug_order.drug_inventory_id ASC) AS subquery) AS drugs,"}
|
22
|
+
recent_prescription.prescription_date
|
23
|
+
FROM temp_patient_outcomes AS outcomes
|
24
|
+
INNER JOIN orders
|
25
|
+
ON orders.patient_id = outcomes.patient_id
|
26
|
+
AND orders.concept_id IN (#{arv_drugs_concept_set.to_sql})
|
27
|
+
AND orders.voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
|
28
|
+
INNER JOIN drug_order
|
29
|
+
ON drug_order.order_id = orders.order_id AND drug_order.quantity > 0 #{site_manager(operator: 'AND', column: 'drug_order.site_id', location: @location)}
|
30
|
+
/* Only select drugs prescribed on the last prescription day */
|
31
|
+
INNER JOIN (
|
32
|
+
SELECT patient_id, DATE(MAX(start_date)) AS prescription_date
|
33
|
+
FROM orders
|
34
|
+
INNER JOIN drug_order
|
35
|
+
ON drug_order.order_id = orders.order_id
|
36
|
+
AND drug_order.quantity > 0 #{site_manager(operator: 'AND', column: 'drug_order.site_id', location: @location)}
|
37
|
+
WHERE orders.voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
|
38
|
+
AND orders.concept_id IN (#{arv_drugs_concept_set.to_sql})
|
39
|
+
AND orders.start_date < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
|
40
|
+
AND orders.patient_id IN (
|
41
|
+
SELECT patient_id FROM temp_patient_outcomes WHERE cum_outcome = 'On antiretrovirals' #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
|
42
|
+
)
|
43
|
+
GROUP BY orders.patient_id
|
44
|
+
) AS recent_prescription
|
45
|
+
ON recent_prescription.patient_id = orders.patient_id
|
46
|
+
AND orders.start_date
|
47
|
+
BETWEEN recent_prescription.prescription_date
|
48
|
+
AND #{interval_manager(date: 'recent_prescription.prescription_date', value: 1, interval: 'DAY', operator: '+')}
|
49
|
+
GROUP BY orders.patient_id #{@adapter == 'mysql2' ? '' : ', recent_prescription.prescription_date'}
|
50
|
+
) AS prescriptions
|
51
|
+
LEFT JOIN (
|
52
|
+
SELECT #{@adapter == 'mysql2' ? 'GROUP_CONCAT(drug.drug_id ORDER BY drug.drug_id ASC) AS drugs,' : "(SELECT STRING_AGG(drug_id::VARCHAR, ',') FROM (SELECT drug.drug_id FROM drug ORDER BY drug.drug_id ASC) AS subquery) AS drugs,"}
|
53
|
+
regimen_name.name AS name
|
54
|
+
FROM moh_regimen_combination AS combo
|
55
|
+
INNER JOIN moh_regimen_combination_drug AS drug USING (regimen_combination_id)
|
56
|
+
INNER JOIN moh_regimen_name AS regimen_name USING (regimen_name_id)
|
57
|
+
GROUP BY combo.regimen_combination_id, regimen_name.name#{' '}
|
58
|
+
) AS regimens
|
59
|
+
ON regimens.drugs = prescriptions.drugs
|
60
|
+
SQL
|
61
|
+
end
|
62
|
+
|
63
|
+
def arv_drugs_concept_set
|
64
|
+
@arv_drugs_concept_set ||= ::ConceptSet.where(set: concept_name('Antiretroviral drugs'))
|
65
|
+
.select(:concept_id)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MalawiHivProgramReports
|
4
|
+
module Cohort
|
5
|
+
class SideEffects
|
6
|
+
include MalawiHivProgramReports::Utils::ModelUtils
|
7
|
+
include MalawiHivProgramReports::Adapters::Moh::Custom
|
8
|
+
|
9
|
+
def update_side_effects(date, location)
|
10
|
+
@location = location
|
11
|
+
@adapter = ActiveRecord::Base.connection.adapter_name.downcase
|
12
|
+
initialize_table
|
13
|
+
|
14
|
+
load_patients_with_side_effects(date)
|
15
|
+
load_patients_without_side_effects(date)
|
16
|
+
load_patients_missing_side_effects(date)
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize_table
|
20
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
21
|
+
DROP TABLE IF EXISTS temp_patient_side_effects
|
22
|
+
SQL
|
23
|
+
|
24
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
25
|
+
CREATE TABLE temp_patient_side_effects (
|
26
|
+
patient_id INT PRIMARY KEY,
|
27
|
+
has_se VARCHAR(120) NOT NULL,
|
28
|
+
site_id INT DEFAULT NULL
|
29
|
+
)
|
30
|
+
SQL
|
31
|
+
ActiveRecord::Base.connection.execute 'CREATE INDEX tpse_site_id ON temp_patient_side_effects(site_id)'
|
32
|
+
ActiveRecord::Base.connection.execute 'CREATE INDEX idx_se ON temp_patient_side_effects(has_se)'
|
33
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
34
|
+
CREATE INDEX idx_side_effects ON temp_patient_side_effects (patient_id, has_se, site_id)
|
35
|
+
SQL
|
36
|
+
end
|
37
|
+
|
38
|
+
def load_patients_with_side_effects(date)
|
39
|
+
date = ActiveRecord::Base.connection.quote(date)
|
40
|
+
|
41
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
42
|
+
INSERT INTO temp_patient_side_effects(patient_id, has_se, site_id)
|
43
|
+
SELECT patients.patient_id, 'Yes', #{@location}
|
44
|
+
FROM temp_earliest_start_date AS patients
|
45
|
+
INNER JOIN temp_patient_outcomes
|
46
|
+
ON temp_patient_outcomes.patient_id = patients.patient_id
|
47
|
+
AND LOWER(temp_patient_outcomes.cum_outcome) = LOWER('On antiretrovirals') #{site_manager(operator: 'AND', column: 'temp_patient_outcomes.site_id', location: @location)}
|
48
|
+
INNER JOIN obs AS side_effects_group
|
49
|
+
ON side_effects_group.person_id = patients.patient_id
|
50
|
+
AND side_effects_group.concept_id = #{art_side_effects.concept_id}
|
51
|
+
AND side_effects_group.voided = 0 #{site_manager(operator: 'AND', column: 'side_effects_group.site_id', location: @location)}
|
52
|
+
/* Limit check to last visit before #{date} */
|
53
|
+
INNER JOIN (
|
54
|
+
SELECT person_id, MAX(obs_datetime) AS obs_datetime FROM obs
|
55
|
+
WHERE concept_id = #{art_side_effects.concept_id}
|
56
|
+
/* Side effects on initial visit are treated as contra-indications */
|
57
|
+
AND obs_datetime < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
|
58
|
+
AND voided = 0 #{site_manager(operator: 'AND', column: 'obs.site_id', location: @location)}
|
59
|
+
AND person_id IN (SELECT patient_id FROM temp_patient_outcomes WHERE LOWER(cum_outcome) = LOWER('On antiretrovirals') #{site_manager(operator: 'AND', column: 'site_id', location: @location)})
|
60
|
+
GROUP BY person_id
|
61
|
+
) AS last_visit
|
62
|
+
ON last_visit.person_id = side_effects_group.person_id
|
63
|
+
AND last_visit.obs_datetime = side_effects_group.obs_datetime
|
64
|
+
AND last_visit.obs_datetime >= #{interval_manager(date: 'patients.date_enrolled', value: 1, interval: 'DAY', operator: '+')}
|
65
|
+
INNER JOIN obs AS side_effects
|
66
|
+
ON side_effects.person_id = patients.patient_id
|
67
|
+
AND side_effects_group.obs_id = side_effects.obs_group_id
|
68
|
+
AND side_effects.value_coded = #{yes.concept_id}
|
69
|
+
AND side_effects.voided = 0 #{site_manager(operator: 'AND', column: 'side_effects.site_id', location: @location)}
|
70
|
+
WHERE patients.date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
|
71
|
+
GROUP BY patients.patient_id
|
72
|
+
SQL
|
73
|
+
end
|
74
|
+
|
75
|
+
def load_patients_without_side_effects(date)
|
76
|
+
date = ActiveRecord::Base.connection.quote(date)
|
77
|
+
|
78
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
79
|
+
INSERT INTO temp_patient_side_effects
|
80
|
+
SELECT patients.patient_id, 'No', #{@location}
|
81
|
+
FROM temp_earliest_start_date AS patients
|
82
|
+
INNER JOIN temp_patient_outcomes
|
83
|
+
ON temp_patient_outcomes.patient_id = patients.patient_id
|
84
|
+
AND LOWER(temp_patient_outcomes.cum_outcome) = LOWER('On antiretrovirals') #{site_manager(operator: 'AND', column: 'temp_patient_outcomes.site_id', location: @location)}
|
85
|
+
INNER JOIN obs AS side_effects_group
|
86
|
+
ON side_effects_group.person_id = patients.patient_id
|
87
|
+
AND side_effects_group.concept_id = #{art_side_effects.concept_id}
|
88
|
+
AND side_effects_group.voided = 0 #{site_manager(operator: 'AND', column: 'side_effects_group.site_id', location: @location)}
|
89
|
+
/* Limit check to last visit before #{date} */
|
90
|
+
INNER JOIN (
|
91
|
+
SELECT person_id, MAX(obs_datetime) AS obs_datetime FROM obs
|
92
|
+
WHERE concept_id = #{art_side_effects.concept_id}
|
93
|
+
/* Side effects on initial visit are treated as contra-indications */
|
94
|
+
AND obs_datetime < #{interval_manager(date:, value: 1, interval: 'DAY', operator: '+')}
|
95
|
+
AND voided = 0 #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
|
96
|
+
AND person_id IN (
|
97
|
+
SELECT patient_id FROM temp_patient_outcomes WHERE LOWER(cum_outcome) = LOWER('On antiretrovirals') #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
|
98
|
+
)
|
99
|
+
GROUP BY person_id
|
100
|
+
) AS last_visit
|
101
|
+
ON last_visit.person_id = side_effects_group.person_id
|
102
|
+
AND last_visit.obs_datetime = side_effects_group.obs_datetime
|
103
|
+
AND last_visit.obs_datetime >= #{interval_manager(date: 'patients.date_enrolled', value: 1, interval: 'DAY', operator: '+')}
|
104
|
+
INNER JOIN obs AS side_effects
|
105
|
+
ON side_effects.person_id = patients.patient_id
|
106
|
+
AND side_effects_group.obs_id = side_effects.obs_group_id
|
107
|
+
AND side_effects.value_coded = #{no.concept_id}
|
108
|
+
AND side_effects.voided = 0 #{site_manager(operator: 'AND', column: 'side_effects.site_id', location: @location)}
|
109
|
+
WHERE patients.date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'patients.site_id', location: @location)}
|
110
|
+
AND patients.patient_id NOT IN (
|
111
|
+
SELECT patient_id FROM temp_patient_side_effects WHERE has_se = 'Yes' #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
|
112
|
+
)
|
113
|
+
GROUP BY patients.patient_id
|
114
|
+
SQL
|
115
|
+
end
|
116
|
+
|
117
|
+
def load_patients_missing_side_effects(date)
|
118
|
+
date = ActiveRecord::Base.connection.quote(date)
|
119
|
+
|
120
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
121
|
+
INSERT INTO temp_patient_side_effects
|
122
|
+
SELECT patient_id, 'Unknown', #{@location} FROM temp_earliest_start_date
|
123
|
+
WHERE date_enrolled <= #{date} #{site_manager(operator: 'AND', column: 'site_id', location: @location)}
|
124
|
+
AND patient_id NOT IN (SELECT patient_id FROM temp_patient_side_effects #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)})
|
125
|
+
SQL
|
126
|
+
end
|
127
|
+
|
128
|
+
def yes
|
129
|
+
@yes ||= concept_name('Yes')
|
130
|
+
end
|
131
|
+
|
132
|
+
def no
|
133
|
+
@no ||= concept_name('No')
|
134
|
+
end
|
135
|
+
|
136
|
+
def art_side_effects
|
137
|
+
@art_side_effects ||= concept_name('Malawi ART Side Effects')
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|