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,456 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MalawiHivProgramReports
|
|
4
|
+
module Clinic
|
|
5
|
+
class RegimenSwitch
|
|
6
|
+
def initialize(start_date:, end_date:, **kwargs)
|
|
7
|
+
@start_date = start_date
|
|
8
|
+
@end_date = end_date
|
|
9
|
+
@occupation = kwargs[:occupation]
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def regimen_switch(pepfar)
|
|
13
|
+
swicth_report(pepfar)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def regimen_report(type)
|
|
17
|
+
RegimenDispensationData.new(type:, start_date: @start_date,
|
|
18
|
+
end_date: @end_date, occupation: @occupation)
|
|
19
|
+
.find_report
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def latest_regimen_dispensed(rebuild_outcome)
|
|
23
|
+
if rebuild_outcome || @occupation.present?
|
|
24
|
+
MalawiHivProgramReports::Moh::CohortBuilder.new(outcomes_definition: 'moh')
|
|
25
|
+
.init_temporary_tables(@start_date, @end_date, @occupation)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
latest_regimens
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def latest_regimens
|
|
34
|
+
pills_dispensed = ::ConceptName.find_by_name('Amount of drug dispensed').concept_id
|
|
35
|
+
patient_identifier_type = ::PatientIdentifierType.find_by_name('ARV Number').id
|
|
36
|
+
|
|
37
|
+
arv_dispensentions = ActiveRecord::Base.connection.select_all <<~SQL
|
|
38
|
+
SELECT
|
|
39
|
+
o.patient_id, drug.drug_id, o.order_id, i.identifier,
|
|
40
|
+
drug.name, d.quantity, o.start_date, obs.value_numeric,
|
|
41
|
+
person.birthdate, person.gender
|
|
42
|
+
FROM orders o
|
|
43
|
+
INNER JOIN drug_order d ON d.order_id = o.order_id AND d.quantity > 0
|
|
44
|
+
INNER JOIN drug ON drug.drug_id = d.drug_inventory_id
|
|
45
|
+
INNER JOIN arv_drug On arv_drug.drug_id = drug.drug_id
|
|
46
|
+
INNER JOIN temp_patient_outcomes t ON o.patient_id = t.patient_id AND t.cum_outcome = 'On antiretrovirals'
|
|
47
|
+
INNER JOIN person ON person.person_id = o.patient_id AND person.voided = 0
|
|
48
|
+
INNER JOIN (
|
|
49
|
+
SELECT MAX(o.start_date) start_date, o.patient_id
|
|
50
|
+
FROM orders o
|
|
51
|
+
INNER JOIN drug_order dor ON dor.order_id = o.order_id AND dor.quantity > 0
|
|
52
|
+
AND dor.drug_inventory_id IN (SELECT drug_id FROM arv_drug)
|
|
53
|
+
WHERE o.voided = 0
|
|
54
|
+
AND o.start_date <= '#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
|
|
55
|
+
AND o.start_date >= '#{@start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
|
|
56
|
+
GROUP BY o.patient_id
|
|
57
|
+
) lor ON lor.start_date = o.start_date AND lor.patient_id = o.patient_id
|
|
58
|
+
LEFT JOIN obs on obs.order_id = o.order_id AND obs.concept_id=#{pills_dispensed} AND obs.voided = 0
|
|
59
|
+
LEFT JOIN patient_identifier i ON i.patient_id = o.patient_id
|
|
60
|
+
AND i.identifier_type = #{patient_identifier_type} AND i.voided = 0
|
|
61
|
+
WHERE o.voided = 0
|
|
62
|
+
AND o.start_date <= '#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
|
|
63
|
+
AND o.start_date >= '#{@start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
|
|
64
|
+
ORDER BY o.patient_id
|
|
65
|
+
SQL
|
|
66
|
+
|
|
67
|
+
patient_list = arv_dispensentions.map { |d| d['patient_id'] }.uniq.push(0)
|
|
68
|
+
|
|
69
|
+
@latest_vl = latest_vl_orders(patient_list)
|
|
70
|
+
@latest_result = latest_vl_results(patient_list)
|
|
71
|
+
|
|
72
|
+
formated_data = {}
|
|
73
|
+
|
|
74
|
+
(arv_dispensentions || []).each do |data|
|
|
75
|
+
dispensation_date = data['start_date'].to_date
|
|
76
|
+
patient_id = data['patient_id'].to_i
|
|
77
|
+
order_id = data['order_id'].to_i
|
|
78
|
+
# drug_id = data['drug_id'].to_i
|
|
79
|
+
medication = data['name']
|
|
80
|
+
quantity = data['quantity'].to_f
|
|
81
|
+
value_numeric = data['value_numeric'].to_f
|
|
82
|
+
drug_id = data['drug_id'].to_i
|
|
83
|
+
# find the latest vl result for the patient from the array of vl results {patient_id: number, order_date: date}
|
|
84
|
+
latest_vl = @latest_vl.select { |vl| vl['patient_id'] == patient_id }&.first
|
|
85
|
+
latest_result = @latest_result.select { |vl| vl['patient_id'] == patient_id }&.first
|
|
86
|
+
|
|
87
|
+
formated_data[patient_id] = {} if formated_data[patient_id].blank?
|
|
88
|
+
if formated_data[patient_id][order_id].blank?
|
|
89
|
+
formated_data[patient_id][order_id] = {
|
|
90
|
+
name: medication,
|
|
91
|
+
quantity:,
|
|
92
|
+
dispensation_date:,
|
|
93
|
+
identifier: data['identifier'],
|
|
94
|
+
gender: data['gender'],
|
|
95
|
+
birthdate: data['birthdate'],
|
|
96
|
+
drug_id:,
|
|
97
|
+
pack_sizes: [],
|
|
98
|
+
vl_latest_order_date: latest_vl.present? ? latest_vl['order_date']&.to_date : 'N/A',
|
|
99
|
+
vl_latest_result_date: latest_result.present? ? latest_result['result_date']&.to_date : 'N/A',
|
|
100
|
+
vl_latest_result: latest_result.present? ? latest_result['result'] : 'N/A'
|
|
101
|
+
}
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
formated_data[patient_id][order_id][:pack_sizes] << value_numeric
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
formated_data
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def regimen_data
|
|
111
|
+
::EncounterType.find_by_name('DISPENSING').id
|
|
112
|
+
arv_concept_id = ::ConceptName.find_by_name('Antiretroviral drugs').concept_id
|
|
113
|
+
|
|
114
|
+
drug_ids = ::Drug.joins('INNER JOIN concept_set s ON s.concept_id = drug.concept_id')
|
|
115
|
+
.where('s.concept_set = ?', arv_concept_id).map(&:drug_id)
|
|
116
|
+
|
|
117
|
+
ActiveRecord::Base.connection.execute('drop table if exists tmp_latest_arv_dispensation ;')
|
|
118
|
+
|
|
119
|
+
ActiveRecord::Base.connection.execute("
|
|
120
|
+
create table tmp_latest_arv_dispensation
|
|
121
|
+
SELECT patient_id,DATE(MAX(start_date)) as start_date
|
|
122
|
+
FROM orders INNER JOIN drug_order t USING (order_id)
|
|
123
|
+
WHERE
|
|
124
|
+
(
|
|
125
|
+
start_date BETWEEN '#{@start_date.to_date.strftime('%Y-%m-%d 00:00:00')}' AND '#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
|
|
126
|
+
AND t.drug_inventory_id IN (#{drug_ids.join(',')}) AND t.quantity > 0
|
|
127
|
+
)
|
|
128
|
+
group by patient_id")
|
|
129
|
+
|
|
130
|
+
ActiveRecord::Base.connection.execute('create index lad_patient_id_and_start_date on tmp_latest_arv_dispensation (start_date, patient_id);')
|
|
131
|
+
|
|
132
|
+
arv_dispensentions = ActiveRecord::Base.connection.select_all <<~SQL
|
|
133
|
+
SELECT
|
|
134
|
+
o.patient_id patient_id, o.start_date, o.order_id,
|
|
135
|
+
d.quantity, drug.name
|
|
136
|
+
FROM orders o
|
|
137
|
+
INNER JOIN drug_order d ON o.order_id = d.order_id
|
|
138
|
+
INNER JOIN drug ON d.drug_inventory_id = drug.drug_id
|
|
139
|
+
INNER JOIN tmp_latest_arv_dispensation k on (o.patient_id = k.patient_id and DATE(o.start_date) = k.start_date)
|
|
140
|
+
WHERE d.drug_inventory_id IN(#{drug_ids.join(',')})
|
|
141
|
+
AND d.quantity > 0 AND o.voided = 0 AND o.start_date BETWEEN '#{@start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
|
|
142
|
+
AND '#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}' GROUP BY o.order_id;
|
|
143
|
+
SQL
|
|
144
|
+
|
|
145
|
+
patient_ids = []
|
|
146
|
+
(arv_dispensentions || []).each do |data|
|
|
147
|
+
patient_ids << data['patient_id'].to_i
|
|
148
|
+
end
|
|
149
|
+
return [] if patient_ids.blank?
|
|
150
|
+
|
|
151
|
+
ActiveRecord::Base.connection.select_all <<~SQL
|
|
152
|
+
SELECT
|
|
153
|
+
`p`.`patient_id` AS `patient_id`,
|
|
154
|
+
cast(patient_date_enrolled(`p`.`patient_id`) as date) AS `date_enrolled`,
|
|
155
|
+
date_antiretrovirals_started(`p`.`patient_id`, min(`s`.`start_date`)) AS `earliest_start_date`
|
|
156
|
+
FROM
|
|
157
|
+
((`patient_program` `p`
|
|
158
|
+
LEFT JOIN `person` `pe` ON ((`pe`.`person_id` = `p`.`patient_id`))
|
|
159
|
+
LEFT JOIN `patient_state` `s` ON ((`p`.`patient_program_id` = `s`.`patient_program_id`)))
|
|
160
|
+
LEFT JOIN `person` ON ((`person`.`person_id` = `p`.`patient_id`)))
|
|
161
|
+
WHERE
|
|
162
|
+
((`p`.`voided` = 0)
|
|
163
|
+
AND (`s`.`voided` = 0)
|
|
164
|
+
AND (`p`.`program_id` = 1)
|
|
165
|
+
AND (`s`.`state` = 7))
|
|
166
|
+
AND (`s`.`start_date` <= '#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
|
|
167
|
+
AND p.patient_id IN(#{patient_ids.join(',')}))
|
|
168
|
+
GROUP BY `p`.`patient_id`;
|
|
169
|
+
SQL
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def arv_dispensention_data(patient_id)
|
|
173
|
+
::EncounterType.find_by_name('DISPENSING').id
|
|
174
|
+
arv_concept_id = ::ConceptName.find_by_name('Antiretroviral drugs').concept_id
|
|
175
|
+
|
|
176
|
+
drug_ids = ::Drug.joins('INNER JOIN concept_set s ON s.concept_id = drug.concept_id')
|
|
177
|
+
.where('s.concept_set = ?', arv_concept_id).map(&:drug_id)
|
|
178
|
+
|
|
179
|
+
ActiveRecord::Base.connection.select_all <<~SQL
|
|
180
|
+
SELECT
|
|
181
|
+
o.patient_id, drug.name, d.quantity, o.start_date
|
|
182
|
+
FROM orders o
|
|
183
|
+
INNER JOIN drug_order d ON d.order_id = o.order_id
|
|
184
|
+
INNER JOIN drug ON drug.drug_id = d.drug_inventory_id
|
|
185
|
+
WHERE d.drug_inventory_id IN(#{drug_ids.join(',')})
|
|
186
|
+
AND o.patient_id = #{patient_id} AND
|
|
187
|
+
d.quantity > 0 AND o.voided = 0 AND DATE(o.start_date) = (
|
|
188
|
+
SELECT DATE(MAX(start_date)) FROM orders
|
|
189
|
+
INNER JOIN drug_order t USING(order_id)
|
|
190
|
+
WHERE patient_id = o.patient_id
|
|
191
|
+
AND (
|
|
192
|
+
start_date BETWEEN '#{@start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
|
|
193
|
+
AND '#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
|
|
194
|
+
AND t.drug_inventory_id IN(#{drug_ids.join(',')}) AND quantity > 0
|
|
195
|
+
)
|
|
196
|
+
) GROUP BY (o.order_id)
|
|
197
|
+
SQL
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def current_regimen(type)
|
|
201
|
+
data = regimen_data
|
|
202
|
+
|
|
203
|
+
clients = {}
|
|
204
|
+
(data || []).each do |r|
|
|
205
|
+
patient_id = r['patient_id'].to_i
|
|
206
|
+
|
|
207
|
+
outcome_status = if type == 'pepfar'
|
|
208
|
+
ActiveRecord::Base.connection.select_one <<~SQL
|
|
209
|
+
SELECT pepfar_patient_outcome(#{patient_id}, '#{@end_date.to_date}') outcome;
|
|
210
|
+
SQL
|
|
211
|
+
|
|
212
|
+
else
|
|
213
|
+
ActiveRecord::Base.connection.select_one <<~SQL
|
|
214
|
+
SELECT patient_outcome(#{patient_id}, '#{@end_date.to_date}') outcome;
|
|
215
|
+
SQL
|
|
216
|
+
|
|
217
|
+
end
|
|
218
|
+
next unless outcome_status['outcome'] == 'On antiretrovirals'
|
|
219
|
+
|
|
220
|
+
medications = arv_dispensention_data(patient_id)
|
|
221
|
+
|
|
222
|
+
begin
|
|
223
|
+
visit_date = medications.first['start_date'].to_date
|
|
224
|
+
rescue StandardError
|
|
225
|
+
next
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
curr_reg = ActiveRecord::Base.connection.select_one <<~SQL
|
|
229
|
+
SELECT patient_current_regimen(#{patient_id}, '#{@end_date.to_date}') current_regimen
|
|
230
|
+
SQL
|
|
231
|
+
|
|
232
|
+
next unless visit_date >= @start_date.to_date && visit_date <= @end_date.to_date
|
|
233
|
+
|
|
234
|
+
if clients[patient_id].blank?
|
|
235
|
+
demo = ActiveRecord::Base.connection.select_one <<~SQL
|
|
236
|
+
SELECT
|
|
237
|
+
p.birthdate, p.gender, i.identifier arv_number,
|
|
238
|
+
n.given_name, n.family_name
|
|
239
|
+
FROM person p
|
|
240
|
+
LEFT JOIN person_name n ON n.person_id = p.person_id AND n.voided = 0
|
|
241
|
+
LEFT JOIN patient_identifier i ON i.patient_id = p.person_id
|
|
242
|
+
AND i.identifier_type = 4 AND i.voided = 0
|
|
243
|
+
WHERE p.person_id = #{patient_id} GROUP BY p.person_id
|
|
244
|
+
ORDER BY n.date_created DESC, i.date_created DESC;
|
|
245
|
+
SQL
|
|
246
|
+
|
|
247
|
+
viral_load = latest_vl_results([patient_id])
|
|
248
|
+
clients[patient_id] = {
|
|
249
|
+
arv_number: demo['arv_number'],
|
|
250
|
+
given_name: demo['given_name'],
|
|
251
|
+
family_name: demo['family_name'],
|
|
252
|
+
birthdate: demo['birthdate'],
|
|
253
|
+
gender: demo['gender'] == 'M' ? 'M' : maternal_status(patient_id, demo['gender']),
|
|
254
|
+
current_regimen: curr_reg['current_regimen'],
|
|
255
|
+
current_weight: current_weight(patient_id),
|
|
256
|
+
art_start_date: r['earliest_start_date'],
|
|
257
|
+
medication: [],
|
|
258
|
+
vl_result: viral_load ? viral_load['result'] : nil,
|
|
259
|
+
vl_result_date: viral_load ? viral_load['result_date'] : nil
|
|
260
|
+
}
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
(medications || []).each do |med|
|
|
264
|
+
clients[patient_id][:medication] << {
|
|
265
|
+
medication: med['name'],
|
|
266
|
+
quantity: med['quantity'],
|
|
267
|
+
start_date: visit_date
|
|
268
|
+
}
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
clients
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def swicth_report(pepfar)
|
|
276
|
+
clients = {}
|
|
277
|
+
data = regimen_data
|
|
278
|
+
pepfar_outcome_builder(pepfar.blank? ? 'moh' : 'pepfar')
|
|
279
|
+
|
|
280
|
+
(data || []).each do |r|
|
|
281
|
+
patient_id = r['patient_id'].to_i
|
|
282
|
+
medications = arv_dispensention_data(patient_id)
|
|
283
|
+
|
|
284
|
+
outcome_status = ActiveRecord::Base.connection.select_one <<~SQL
|
|
285
|
+
SELECT cum_outcome FROM temp_patient_outcomes WHERE patient_id = #{patient_id};
|
|
286
|
+
SQL
|
|
287
|
+
|
|
288
|
+
next if outcome_status.blank?
|
|
289
|
+
next if outcome_status['cum_outcome'].blank?
|
|
290
|
+
next unless outcome_status['cum_outcome'] == 'On antiretrovirals'
|
|
291
|
+
|
|
292
|
+
visit_date = medications.first['start_date']
|
|
293
|
+
visit_date.blank? ? next : (visit_date = visit_date.to_date)
|
|
294
|
+
|
|
295
|
+
next unless visit_date >= @start_date.to_date && visit_date <= @end_date.to_date
|
|
296
|
+
|
|
297
|
+
prev_reg = ActiveRecord::Base.connection.select_one <<~SQL
|
|
298
|
+
SELECT patient_current_regimen(#{patient_id}, '#{(visit_date - 1.day).to_date}') previous_regimen
|
|
299
|
+
SQL
|
|
300
|
+
|
|
301
|
+
current_reg = ActiveRecord::Base.connection.select_one <<~SQL
|
|
302
|
+
SELECT patient_current_regimen(#{patient_id}, '#{visit_date}') current_regimen
|
|
303
|
+
SQL
|
|
304
|
+
|
|
305
|
+
next if prev_reg['previous_regimen'] == current_reg['current_regimen']
|
|
306
|
+
next if prev_reg['previous_regimen'] == 'N/A'
|
|
307
|
+
|
|
308
|
+
if clients[patient_id].blank?
|
|
309
|
+
demo = ActiveRecord::Base.connection.select_one <<~SQL
|
|
310
|
+
SELECT
|
|
311
|
+
p.birthdate, p.gender, i.identifier arv_number,
|
|
312
|
+
n.given_name, n.family_name, p.person_id
|
|
313
|
+
FROM person p
|
|
314
|
+
LEFT JOIN person_name n ON n.person_id = p.person_id AND n.voided = 0
|
|
315
|
+
LEFT JOIN patient_identifier i ON i.patient_id = p.person_id
|
|
316
|
+
AND i.identifier_type = 4 AND i.voided = 0
|
|
317
|
+
WHERE p.person_id = #{patient_id} GROUP BY p.person_id
|
|
318
|
+
ORDER BY n.date_created DESC, i.date_created DESC
|
|
319
|
+
SQL
|
|
320
|
+
|
|
321
|
+
clients[patient_id] = {
|
|
322
|
+
arv_number: (demo['arv_number'].blank? ? 'N/A' : demo['arv_number']),
|
|
323
|
+
given_name: demo['given_name'],
|
|
324
|
+
family_name: demo['family_name'],
|
|
325
|
+
birthdate: demo['birthdate'],
|
|
326
|
+
gender: demo['gender'],
|
|
327
|
+
previous_regimen: prev_reg['previous_regimen'],
|
|
328
|
+
current_regimen: current_reg['current_regimen'],
|
|
329
|
+
patient_type: get_patient_type(demo['person_id'], pepfar),
|
|
330
|
+
current_weight: current_weight(demo['person_id']),
|
|
331
|
+
art_start_date: r['earliest_start_date'],
|
|
332
|
+
medication: []
|
|
333
|
+
}
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
(medications || []).each do |m|
|
|
337
|
+
clients[patient_id][:medication] << {
|
|
338
|
+
medication: m['name'], quantity: m['quantity'],
|
|
339
|
+
start_date: visit_date
|
|
340
|
+
}
|
|
341
|
+
end
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
clients
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def get_patient_type(patient_id, pepfar)
|
|
348
|
+
return nil unless pepfar
|
|
349
|
+
|
|
350
|
+
concept_id = ::ConceptName.find_by_name('Type of patient').concept_id
|
|
351
|
+
ext_id = ::ConceptName.find_by_name('External consultation').concept_id
|
|
352
|
+
obs = ::Observation.where(concept_id:, value_coded: ext_id, person_id: patient_id)
|
|
353
|
+
(obs.blank? ? 'Resident' : 'External')
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
def pepfar_outcome_builder(repport_type = 'moh')
|
|
357
|
+
cohort_builder = MalawiHivProgramReports::Moh::CohortDisaggregated.new(name: 'Regimen switch', type: repport_type,
|
|
358
|
+
start_date: @start_date.to_date,
|
|
359
|
+
end_date: @end_date.to_date, rebuild: true,
|
|
360
|
+
occupation: @occupation)
|
|
361
|
+
cohort_builder.rebuild_outcomes(repport_type)
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def current_weight(patient_id)
|
|
365
|
+
weight_concept = ::ConceptName.find_by_name('Weight (kg)').concept_id
|
|
366
|
+
obs = ::Observation.where("person_id = ? AND concept_id = ?
|
|
367
|
+
AND obs_datetime <= ? AND (value_numeric IS NOT NULL OR value_text IS NOT NULL)",
|
|
368
|
+
patient_id, weight_concept, @end_date.to_date.strftime('%Y-%m-%d 23:59:59'))
|
|
369
|
+
.order('obs_datetime DESC, date_created DESC')
|
|
370
|
+
|
|
371
|
+
return nil if obs.blank?
|
|
372
|
+
|
|
373
|
+
(obs.first.value_numeric.blank? ? obs.first.value_text : obs.first.value_numeric)
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# def vl_result(patient_id)
|
|
377
|
+
# ActiveRecord::Base.connection.select_one <<~SQL
|
|
378
|
+
# SELECT lab_result_obs.obs_datetime AS result_date,
|
|
379
|
+
# CONCAT (COALESCE(measure.value_modifier, '='),' ',COALESCE(measure.value_numeric, measure.value_text, '')) as result
|
|
380
|
+
# FROM obs AS lab_result_obs
|
|
381
|
+
# INNER JOIN orders
|
|
382
|
+
# ON orders.order_id = lab_result_obs.order_id
|
|
383
|
+
# AND orders.voided = 0
|
|
384
|
+
# INNER JOIN obs AS measure
|
|
385
|
+
# ON measure.obs_group_id = lab_result_obs.obs_id
|
|
386
|
+
# AND measure.voided = 0
|
|
387
|
+
# INNER JOIN (
|
|
388
|
+
# SELECT concept_id, name
|
|
389
|
+
# FROM concept_name
|
|
390
|
+
# INNER JOIN concept USING (concept_id)
|
|
391
|
+
# WHERE concept.retired = 0
|
|
392
|
+
# AND name NOT LIKE 'Lab test result'
|
|
393
|
+
# GROUP BY concept_id
|
|
394
|
+
# ) AS measure_concept
|
|
395
|
+
# ON measure_concept.concept_id = measure.concept_id
|
|
396
|
+
# WHERE lab_result_obs.voided = 0
|
|
397
|
+
# AND measure.person_id = #{patient_id}
|
|
398
|
+
# AND (measure.value_numeric IS NOT NULL || measure.value_text IS NOT NULL)
|
|
399
|
+
# AND lab_result_obs.obs_datetime <= '#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}'
|
|
400
|
+
# ORDER BY lab_result_obs.obs_datetime DESC
|
|
401
|
+
# LIMIT 1
|
|
402
|
+
# SQL
|
|
403
|
+
# end
|
|
404
|
+
|
|
405
|
+
def latest_vl_orders(patient_list)
|
|
406
|
+
ActiveRecord::Base.connection.select_all <<~SQL
|
|
407
|
+
SELECT odr.patient_id, MAX(start_date) AS order_date
|
|
408
|
+
FROM obs o
|
|
409
|
+
INNER JOIN orders odr ON odr.order_id = o.order_id AND odr.voided = 0 AND DATE(odr.start_date) <= '#{@end_date}'
|
|
410
|
+
WHERE o.concept_id = #{::ConceptName.find_by_name('Test Type').concept_id}
|
|
411
|
+
AND o.value_coded = #{::ConceptName.find_by_name('HIV viral load').concept_id}
|
|
412
|
+
AND o.voided = 0
|
|
413
|
+
AND o.person_id IN (#{patient_list.join(',')})
|
|
414
|
+
GROUP BY odr.patient_id
|
|
415
|
+
SQL
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def latest_vl_results(patient_list)
|
|
419
|
+
ActiveRecord::Base.connection.select_all <<~SQL
|
|
420
|
+
SELECT o.person_id AS patient_id,
|
|
421
|
+
o.obs_datetime AS result_date,
|
|
422
|
+
CONCAT (COALESCE(o.value_modifier, '='),' ',COALESCE(o.value_numeric, o.value_text, '')) AS result
|
|
423
|
+
FROM obs o
|
|
424
|
+
INNER JOIN (
|
|
425
|
+
SELECT MAX(obs_datetime) AS obs_datetime, person_id
|
|
426
|
+
FROM obs co
|
|
427
|
+
INNER JOIN orders odr ON odr.order_id = co.order_id AND odr.voided = 0
|
|
428
|
+
WHERE co.concept_id = #{::ConceptName.find_by_name('HIV viral load').concept_id}
|
|
429
|
+
AND co.voided = 0
|
|
430
|
+
AND co.obs_datetime <= '#{@end_date}'
|
|
431
|
+
AND (co.value_numeric IS NOT NULL || co.value_text IS NOT NULL)
|
|
432
|
+
AND co.person_id IN (#{patient_list.join(',')})
|
|
433
|
+
GROUP BY co.person_id
|
|
434
|
+
) AS latest_vl ON latest_vl.obs_datetime = o.obs_datetime AND latest_vl.person_id = o.person_id
|
|
435
|
+
INNER JOIN orders odr ON odr.order_id = o.order_id AND odr.voided = 0
|
|
436
|
+
WHERE o.concept_id = #{::ConceptName.find_by_name('HIV viral load').concept_id}
|
|
437
|
+
AND o.voided = 0 AND o.obs_datetime <= '#{@end_date}'
|
|
438
|
+
AND (o.value_numeric IS NOT NULL || o.value_text IS NOT NULL)
|
|
439
|
+
AND o.person_id IN (#{patient_list.join(',')})
|
|
440
|
+
ORDER BY o.obs_datetime DESC
|
|
441
|
+
SQL
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
def maternal_status(patient_id, current_gender)
|
|
445
|
+
return nil if current_gender.blank?
|
|
446
|
+
|
|
447
|
+
result = MalawiHivProgramReports::Pepfar::ViralLoadCoverage2.new(start_date: @start_date,
|
|
448
|
+
end_date: @end_date).vl_maternal_status([patient_id])
|
|
449
|
+
gender = 'FNP'
|
|
450
|
+
gender = 'FP' unless result[:FP].blank?
|
|
451
|
+
gender = 'FBf' unless result[:FBf].blank?
|
|
452
|
+
gender
|
|
453
|
+
end
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
end
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Retrieve patients in a particular regimen and formulation.
|
|
4
|
+
module MalawiHivProgramReports
|
|
5
|
+
module Clinic
|
|
6
|
+
class RegimensAndFormulations
|
|
7
|
+
include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
|
|
8
|
+
|
|
9
|
+
attr_reader :start_date, :end_date, :regimen, :formulation
|
|
10
|
+
|
|
11
|
+
def initialize(start_date:, end_date:, regimen: nil, formulation: 'tablets', **kwargs)
|
|
12
|
+
raise ::InvalidParameterError, 'regimen is required' unless regimen
|
|
13
|
+
|
|
14
|
+
unless %w[granules tablets pellets].include?(formulation)
|
|
15
|
+
raise ::InvalidParameterError, "Invalid formalation: #{formulation}"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
@start_date = start_date.to_date
|
|
19
|
+
@end_date = end_date.to_date
|
|
20
|
+
@formulation = formulation
|
|
21
|
+
@regimen = regimen
|
|
22
|
+
@occupation = kwargs[:occupation]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def find_report
|
|
26
|
+
patients
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def patients
|
|
30
|
+
patients_with_prescriptions.each_with_object([]) do |patient, matching_patients|
|
|
31
|
+
prescribed_drugs = drugs_prescribed_to_patient(patient.patient_id, patient.prescription_date).map(&:drug_id)
|
|
32
|
+
non_matching_drugs = Set.new(drugs) - prescribed_drugs
|
|
33
|
+
|
|
34
|
+
next unless non_matching_drugs.empty?
|
|
35
|
+
|
|
36
|
+
demographics = patient_demographics(patient.patient_id, patient.prescription_date)
|
|
37
|
+
|
|
38
|
+
matching_patients << {
|
|
39
|
+
patient_id: demographics.patient_id,
|
|
40
|
+
arv_number: demographics.arv_number,
|
|
41
|
+
birthdate: demographics.birthdate,
|
|
42
|
+
gender: demographics.gender,
|
|
43
|
+
weight: demographics.weight
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
TABLET_REGIMENS = {
|
|
51
|
+
'0A' => [969, 22],
|
|
52
|
+
'0P' => [1044, 968],
|
|
53
|
+
'2A' => [731],
|
|
54
|
+
'2P' => [732],
|
|
55
|
+
'4A' => [39, 11],
|
|
56
|
+
'4P' => [736, 30],
|
|
57
|
+
'5A' => [735],
|
|
58
|
+
'6A' => [734, 22],
|
|
59
|
+
'7A' => [734, 932],
|
|
60
|
+
'8A' => [39, 932],
|
|
61
|
+
'9A' => [969, 73],
|
|
62
|
+
'9P' => [1044, 74],
|
|
63
|
+
'10A' => [734, 73],
|
|
64
|
+
'11A' => [39, 73],
|
|
65
|
+
'11P' => [736, 74],
|
|
66
|
+
'12A' => [982, 977, 976],
|
|
67
|
+
'13A' => [983],
|
|
68
|
+
'14A' => [984, 982],
|
|
69
|
+
'14P' => [736, 982],
|
|
70
|
+
'15A' => [969, 982],
|
|
71
|
+
'15P' => [1044, 982],
|
|
72
|
+
'16A' => [969, 954],
|
|
73
|
+
'16P' => [1044, 1043],
|
|
74
|
+
'17A' => [969, 11],
|
|
75
|
+
'17P' => [1044, 30]
|
|
76
|
+
}.freeze
|
|
77
|
+
|
|
78
|
+
GRANULES_REGIMENS = {
|
|
79
|
+
'9P' => [1044, 1045],
|
|
80
|
+
'11P' => [736, 1045]
|
|
81
|
+
}.freeze
|
|
82
|
+
|
|
83
|
+
PELLETS_REGIMENS = {
|
|
84
|
+
'9P' => [1044, 979],
|
|
85
|
+
'11P' => [736, 979]
|
|
86
|
+
}.freeze
|
|
87
|
+
|
|
88
|
+
REGIMENS_BY_FORMULATION = {
|
|
89
|
+
'granules' => GRANULES_REGIMENS,
|
|
90
|
+
'pellets' => PELLETS_REGIMENS,
|
|
91
|
+
'tablets' => TABLET_REGIMENS
|
|
92
|
+
}.freeze
|
|
93
|
+
|
|
94
|
+
# Returns drugs in selected regimen and formulation
|
|
95
|
+
def drugs
|
|
96
|
+
REGIMENS_BY_FORMULATION[formulation][regimen]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def patients_with_prescriptions
|
|
100
|
+
return [] if drugs.nil?
|
|
101
|
+
|
|
102
|
+
::DrugOrder.select('orders.patient_id AS patient_id, MAX(start_date) AS prescription_date')
|
|
103
|
+
.joins(:order)
|
|
104
|
+
.joins("LEFT JOIN (#{current_occupation_query}) AS a ON a.person_id = orders.patient_id")
|
|
105
|
+
.where(quantity: 1..Float::INFINITY, drug_inventory_id: drugs)
|
|
106
|
+
.where(occupation_filter(occupation: @occupation, field_name: 'value', table_name: 'a',
|
|
107
|
+
include_clause: false).to_s)
|
|
108
|
+
.merge(treatment_orders)
|
|
109
|
+
.group('orders.patient_id')
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Returns all orders in treatment encounter of HIV PROGRAM
|
|
113
|
+
def treatment_orders
|
|
114
|
+
::Order.joins(:encounter)
|
|
115
|
+
.where(start_date: start_date..end_date)
|
|
116
|
+
.merge(treatment_encounter)
|
|
117
|
+
.or(::Order.joins(:encounter)
|
|
118
|
+
.where(auto_expire_date: start_date..end_date)
|
|
119
|
+
.merge(treatment_encounter))
|
|
120
|
+
.or(::Order.joins(:encounter)
|
|
121
|
+
.where('start_date < ? AND auto_expire_date > ?', start_date, end_date)
|
|
122
|
+
.merge(treatment_encounter))
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def treatment_encounter
|
|
126
|
+
::Encounter.where(encounter_type: ::EncounterType.find_by_name('Treatment'),
|
|
127
|
+
program_id: Constants::PROGRAM_ID)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# Returns drugs prescribed to patient on given day
|
|
131
|
+
def drugs_prescribed_to_patient(patient_id, prescription_date)
|
|
132
|
+
::DrugOrder.select('drug_order.drug_inventory_id AS drug_id')
|
|
133
|
+
.joins(:order)
|
|
134
|
+
.where(quantity: 1..Float::INFINITY, drug_inventory_id: drugs)
|
|
135
|
+
.merge(::Order.joins(:encounter)
|
|
136
|
+
.where(patient_id:, start_date: prescription_date)
|
|
137
|
+
.merge(treatment_encounter))
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def patient_demographics(patient_id, prescription_date)
|
|
141
|
+
::Person.find_by_sql(
|
|
142
|
+
<<~SQL
|
|
143
|
+
SELECT person.person_id AS patient_id, patient_identifier.identifier AS arv_number,
|
|
144
|
+
person.birthdate AS birthdate, person.gender AS gender, obs.value_numeric AS weight
|
|
145
|
+
FROM person
|
|
146
|
+
LEFT JOIN patient_identifier ON patient_identifier.patient_id = person.person_id
|
|
147
|
+
AND patient_identifier.identifier_type = #{arv_number_type_id}
|
|
148
|
+
AND patient_identifier.voided = 0
|
|
149
|
+
LEFT JOIN obs ON obs.person_id = person.person_id
|
|
150
|
+
AND obs.concept_id = #{weight_concept_id}
|
|
151
|
+
AND obs.obs_datetime = (
|
|
152
|
+
SELECT MAX(obs_datetime) FROM obs
|
|
153
|
+
WHERE person_id = #{patient_id}
|
|
154
|
+
AND concept_id = #{weight_concept_id}
|
|
155
|
+
AND obs_datetime <= '#{prescription_date}'
|
|
156
|
+
AND voided = 0
|
|
157
|
+
) AND obs.voided = 0
|
|
158
|
+
WHERE person.person_id = #{patient_id}
|
|
159
|
+
SQL
|
|
160
|
+
).first
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def patient_recent_weight(patient_id, as_of)
|
|
164
|
+
::Observation.select(:value_numeric)
|
|
165
|
+
.where(concept_id: ::ConceptName.find_by_name('Weight (kg)').concept_id,
|
|
166
|
+
person_id: patient_id)
|
|
167
|
+
.where('obs_datetime < ? AND value_numeric IS NOT NULL', as_of)
|
|
168
|
+
.order(obs_datetime: :desc)
|
|
169
|
+
.first
|
|
170
|
+
&.value_numeric
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def weight_concept_id
|
|
174
|
+
@weight_concept_id ||= ::ConceptName.find_by_name('Weight (kg)').concept_id
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def arv_number_type_id
|
|
178
|
+
@arv_number_type_id ||= ::PatientIdentifierType.find_by_name('ARV Number').id
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|