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,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MalawiHivProgramReports
|
4
|
+
module Pepfar
|
5
|
+
# This class is used to generate the SC_CURR report
|
6
|
+
# The current number of ARV drug units (bottles) at the end of the reporting period by ARV drug category
|
7
|
+
class ScCurr
|
8
|
+
DRUG_CATEGORY = {
|
9
|
+
'TLD 30-count bottles' => { drugs: [983], quantity: 30 },
|
10
|
+
'TLD 90-count bottles' => { drugs: [983], quantity: 90 },
|
11
|
+
'TLD 180-count bottles' => { drugs: [983], quantity: 180 },
|
12
|
+
'TLE/400 30-count bottles' => { drugs: [735], quantity: 30 },
|
13
|
+
'TLE/400 90-count bottles' => { drugs: [735], quantity: 90 },
|
14
|
+
'TLE 600/TEE bottles' => { drugs: [11], quantity: 'N/A' },
|
15
|
+
'DTG 10 90-count bottles' => { drugs: [980], quantity: 90 },
|
16
|
+
'DTG 50 30-count bottles' => { drugs: [982], quantity: 30 },
|
17
|
+
'LPV/r 100/25 tabs 60 tabs/bottle' => { drugs: [23, 73, 74, 739, 977, 1045], quantity: 60 },
|
18
|
+
'LPV/r 40/10 (pediatrics) bottles' => { drugs: [94, 979], quantity: 'N/A' },
|
19
|
+
'NVP (adult) bottles' => { drugs: [22, 613], quantity: 'N/A' },
|
20
|
+
'NVP (pediatric) bottles' => { drugs: [21, 817, 968, 971], quantity: 'N/A' },
|
21
|
+
'Other (adult) bottles' => { drugs: [
|
22
|
+
3, 5, 6, 10, 38, 39, 40, 42, 89, 614, 730, 731, 734, 738,
|
23
|
+
814, 815, 932, 933, 934, 952, 954, 955, 957, 969, 976, 978, 984, 1217, 1213, 14
|
24
|
+
], quantity: 'N/A' },
|
25
|
+
'Other (pediatric) bottles' => { drugs: [
|
26
|
+
2, 9, 28, 29, 30, 31, 32, 36, 37, 41, 70, 71, 72, 90, 91,
|
27
|
+
95, 104, 177, 732, 733, 736, 737, 813, 816, 981, 1043, 1044, 1214, 1215
|
28
|
+
], quantity: 'N/A' }
|
29
|
+
}.freeze
|
30
|
+
|
31
|
+
def initialize(start_date:, end_date:, **_kwargs)
|
32
|
+
@start_date = start_date
|
33
|
+
@end_date = end_date
|
34
|
+
@location = _kwargs[:location]
|
35
|
+
end
|
36
|
+
|
37
|
+
def find_report
|
38
|
+
initialize_report
|
39
|
+
process_report
|
40
|
+
# remove the drug_id from the report
|
41
|
+
@report.each { |category| category.delete(:drug_id) }
|
42
|
+
@report
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def initialize_report
|
48
|
+
@report = []
|
49
|
+
DRUG_CATEGORY.each do |category, drug|
|
50
|
+
@report << {
|
51
|
+
category:,
|
52
|
+
drug_id: drug[:drugs],
|
53
|
+
units: 0,
|
54
|
+
quantity: drug[:quantity],
|
55
|
+
granular_spec: []
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# rubocop:disable Metrics/AbcSize
|
61
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
62
|
+
def process_report
|
63
|
+
current_stock.each do |item|
|
64
|
+
# Find the drug category
|
65
|
+
drug_category = @report.find do |category|
|
66
|
+
category[:drug_id].include?(item.drug_id) && category[:quantity] == item.pack_size
|
67
|
+
end
|
68
|
+
drug_category ||= @report.find do |category|
|
69
|
+
category[:drug_id].include?(item.drug_id) && category[:quantity] == 'N/A'
|
70
|
+
end
|
71
|
+
next unless drug_category
|
72
|
+
|
73
|
+
bottles = (item.current_quantity / item.pack_size).to_i
|
74
|
+
drug_category[:units] += bottles
|
75
|
+
# check if the drug is already in the granular_spec
|
76
|
+
granular_spec = drug_category[:granular_spec].find { |spec| spec[:drug_name] == item.drug.name }
|
77
|
+
if granular_spec
|
78
|
+
granular_spec[:units] += bottles
|
79
|
+
else
|
80
|
+
drug_category[:granular_spec] << {
|
81
|
+
drug_name: item.drug.name,
|
82
|
+
units: bottles
|
83
|
+
}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
# rubocop:enable Metrics/AbcSize
|
88
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
89
|
+
|
90
|
+
def current_stock
|
91
|
+
drugs = DRUG_CATEGORY.map { |_, drug| drug[:drugs] }.flatten.uniq
|
92
|
+
::PharmacyBatchItem.where(
|
93
|
+
"expiry_date >= ? AND delivery_date <= ? AND drug_id IN (?) AND current_quantity > 0 #{!@location.blank? ? "AND site_id = #{@location}" : ''}", @end_date, @end_date, drugs
|
94
|
+
)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,163 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MalawiHivProgramReports
|
4
|
+
module Pepfar
|
5
|
+
class TbPrev
|
6
|
+
|
7
|
+
include MalawiHivProgramReports::Adapters::Moh::Custom
|
8
|
+
attr_accessor :start_date, :end_date, :location
|
9
|
+
|
10
|
+
def initialize(start_date:, end_date:, **kwargs)
|
11
|
+
@completion_start_date = start_date&.to_date.strftime('%Y-%m-%d 00:00:00')
|
12
|
+
@completion_end_date = end_date&.to_date.strftime('%Y-%m-%d 23:59:59')
|
13
|
+
@location = kwargs[:location]
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_report
|
17
|
+
data
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def data
|
23
|
+
meds = tb_med_dispensations
|
24
|
+
return {} if meds.blank?
|
25
|
+
|
26
|
+
patient_ids = meds.map { |m| m['patient_id'].to_i }.uniq
|
27
|
+
hiv_program_clients = on_art_in_reporting_period(patient_ids)
|
28
|
+
return if hiv_program_clients.blank?
|
29
|
+
|
30
|
+
clients_in_hiv_program = {}
|
31
|
+
|
32
|
+
hiv_program_clients.each do |e|
|
33
|
+
clients_in_hiv_program[e['patient_id'].to_i] = e
|
34
|
+
end
|
35
|
+
|
36
|
+
clients = {}
|
37
|
+
initiation_start_date = (@completion_start_date&.to_date - 6.month)&.to_date
|
38
|
+
|
39
|
+
meds.each do |m|
|
40
|
+
patient_id = m['patient_id'].to_i
|
41
|
+
inh_start_date = min_inh_start_date(patient_id)
|
42
|
+
next unless inh_start_date&.to_date >= initiation_start_date&.to_date &&
|
43
|
+
inh_start_date&.to_date < @completion_start_date&.to_date
|
44
|
+
|
45
|
+
p = clients_in_hiv_program[patient_id]
|
46
|
+
next if p.blank?
|
47
|
+
|
48
|
+
earliest_start_date = begin
|
49
|
+
p['earliest_start_date']&.to_date
|
50
|
+
rescue StandardError
|
51
|
+
p['date_enrolled']&.to_date
|
52
|
+
end
|
53
|
+
|
54
|
+
quantity = m['quantity'].to_f
|
55
|
+
if clients[patient_id].blank?
|
56
|
+
clients[patient_id] = {
|
57
|
+
date_enrolled: p['date_enrolled']&.to_date,
|
58
|
+
earliest_start_date:,
|
59
|
+
gender: begin
|
60
|
+
p['gender'].upcase.first
|
61
|
+
rescue StandardError
|
62
|
+
'Unknown'
|
63
|
+
end,
|
64
|
+
birthdate: begin
|
65
|
+
p['birthdate']&.to_date
|
66
|
+
rescue StandardError
|
67
|
+
'Unknow'
|
68
|
+
end,
|
69
|
+
age_group: p['age_group'],
|
70
|
+
course_completed: false,
|
71
|
+
client: new_client(earliest_start_date, inh_start_date),
|
72
|
+
quantity: 0
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
clients[patient_id][:quantity] += quantity
|
77
|
+
|
78
|
+
clients[patient_id][:course_completed] = true if clients[patient_id][:quantity] >= 168
|
79
|
+
end
|
80
|
+
|
81
|
+
clients
|
82
|
+
end
|
83
|
+
|
84
|
+
def min_inh_start_date(patient_id)
|
85
|
+
start_date = ActiveRecord::Base.connection.select_one <<-SQL
|
86
|
+
SELECT
|
87
|
+
DATE(MIN(o.start_date)) date
|
88
|
+
FROM person p
|
89
|
+
INNER JOIN orders o ON o.patient_id = p.person_id
|
90
|
+
INNER JOIN drug_order t ON o.order_id = t.order_id
|
91
|
+
INNER JOIN drug d ON d.drug_id = t.drug_inventory_id
|
92
|
+
INNER JOIN encounter e ON e.patient_id = o.patient_id AND e.program_id = 1
|
93
|
+
WHERE o.voided = 0 AND o.patient_id = #{patient_id}
|
94
|
+
AND d.concept_id = 656 AND t.quantity > 0;
|
95
|
+
SQL
|
96
|
+
|
97
|
+
start_date['date']&.to_date
|
98
|
+
end
|
99
|
+
|
100
|
+
def new_client(earliest_start_date, min_date)
|
101
|
+
return 'Previously on ART' if earliest_start_date.blank?
|
102
|
+
|
103
|
+
med_start_date = min_date&.to_date
|
104
|
+
med_end_date = (earliest_start_date&.to_date + 90.day)&.to_date
|
105
|
+
|
106
|
+
return 'New on ART' if med_start_date >= earliest_start_date&.to_date && med_start_date < med_end_date
|
107
|
+
|
108
|
+
'Previously on ART'
|
109
|
+
end
|
110
|
+
|
111
|
+
def client_outcome(patient_id)
|
112
|
+
outcome_string = ActiveRecord::Base.connection.select_one <<-SQL
|
113
|
+
SELECT pepfar_patient_outcome(#{patient_id}, DATE('#{@end_date}')) outcome;
|
114
|
+
SQL
|
115
|
+
|
116
|
+
outcome_string['outcome']
|
117
|
+
end
|
118
|
+
|
119
|
+
def on_art_in_reporting_period(patient_ids)
|
120
|
+
ActiveRecord::Base.connection.select_all <<-SQL
|
121
|
+
select
|
122
|
+
p.patient_id AS patient_id, pe.birthdate, pe.gender,
|
123
|
+
patient_date_enrolled(CAST(p.patient_id AS INT), CAST(#{@location} AS INT)) AS date_enrolled,
|
124
|
+
date_antiretrovirals_started(CAST(p.patient_id AS INT), CAST(MIN(s.start_date) AS DATE), CAST(#{@location} AS INT)) AS earliest_start_date,
|
125
|
+
disaggregated_age_group(CAST(pe.birthdate AS DATE), DATE('#{@completion_end_date}')) age_group
|
126
|
+
from
|
127
|
+
patient_program p
|
128
|
+
left join person pe ON pe.person_id = p.patient_id
|
129
|
+
left join patient_state s ON p.patient_program_id = s.patient_program_id
|
130
|
+
left join person ON person.person_id = p.patient_id
|
131
|
+
where
|
132
|
+
p.voided = 0
|
133
|
+
and s.voided = 0
|
134
|
+
and p.program_id = 1
|
135
|
+
and s.state = 7
|
136
|
+
and #{in_manager(column: 'p.patient_id', values: patient_ids)}
|
137
|
+
group by p.patient_id, pe.birthdate, pe.gender, p.date_enrolled
|
138
|
+
HAVING date_enrolled IS NOT NULL;
|
139
|
+
SQL
|
140
|
+
end
|
141
|
+
|
142
|
+
def tb_med_dispensations
|
143
|
+
initiation_start_date = (@completion_start_date&.to_date - 6.month).strftime('%Y-%m-%d 00:00:00')
|
144
|
+
|
145
|
+
ActiveRecord::Base.connection.select_all <<-SQL
|
146
|
+
SELECT
|
147
|
+
o.patient_id, t.drug_inventory_id, t.quantity,
|
148
|
+
o.start_date, o.auto_expire_date
|
149
|
+
FROM person p
|
150
|
+
INNER JOIN orders o ON o.patient_id = p.person_id
|
151
|
+
INNER JOIN drug_order t ON o.order_id = t.order_id
|
152
|
+
INNER JOIN drug d ON d.drug_id = t.drug_inventory_id
|
153
|
+
INNER JOIN encounter e ON e.patient_id = o.patient_id AND e.program_id = 1
|
154
|
+
WHERE o.voided = 0 AND (o.start_date
|
155
|
+
BETWEEN '#{initiation_start_date}' AND '#{@completion_end_date}')
|
156
|
+
AND d.concept_id = 656 AND t.quantity > 0
|
157
|
+
GROUP BY o.patient_id, t.drug_inventory_id, DATE(o.start_date), t.quantity, o.start_date, o.auto_expire_date
|
158
|
+
ORDER BY o.patient_id;
|
159
|
+
SQL
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MalawiHivProgramReports
|
4
|
+
module Pepfar
|
5
|
+
##
|
6
|
+
# Patients who started TPT just before the start of the current
|
7
|
+
# and have finished within the current reporting period.
|
8
|
+
class TbPrev2
|
9
|
+
attr_reader :start_date, :end_date
|
10
|
+
|
11
|
+
include Utils
|
12
|
+
|
13
|
+
def initialize(start_date:, end_date:, **_kwargs)
|
14
|
+
@start_date = ActiveRecord::Base.connection.quote(start_date)
|
15
|
+
@end_date = ActiveRecord::Base.connection.quote(end_date)
|
16
|
+
end
|
17
|
+
|
18
|
+
def find_report
|
19
|
+
report = init_report
|
20
|
+
patients = group_patients_by_tpt_course(patients_on_tpt)
|
21
|
+
|
22
|
+
load_patients_into_report(report, patients.six_h, '6H') do |patient|
|
23
|
+
# 6H has a constant dosage of 1 pill per day
|
24
|
+
patient['total_pills_taken'].to_i >= FULL_6H_COURSE_PILLS
|
25
|
+
end
|
26
|
+
|
27
|
+
load_patients_into_report(report, patients.three_hp, '3HP') do |patient|
|
28
|
+
# 3HP daily dosages vary by patient weight can't use easily use pills
|
29
|
+
# to determine course completion
|
30
|
+
patient['total_days_on_medication'].days >= FULL_3HP_COURSE_DAYS
|
31
|
+
end
|
32
|
+
|
33
|
+
report
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
FULL_6H_COURSE_PILLS = 168
|
39
|
+
FULL_3HP_COURSE_DAYS = (28.days - 1.day) + (56.days - 1.day) # 82 Days
|
40
|
+
# NOTE: Arrived at 82 days above from how 3HP is prescribed. 1st time prescription
|
41
|
+
# is 1 month (28 days) then 2 months (56 days). And we assume that patients
|
42
|
+
# start taking medication the very same they are given thus we subtract 1.
|
43
|
+
|
44
|
+
def init_report
|
45
|
+
pepfar_age_groups.each_with_object({}) do |age_group, report|
|
46
|
+
report[age_group] = %w[M F Unknown].each_with_object({}) do |gender, gender_sub_report|
|
47
|
+
gender_sub_report[gender] = %w[6H 3HP].each_with_object({}) do |tpt, tpt_sub_report|
|
48
|
+
tpt_sub_report[tpt] = {
|
49
|
+
started_new_on_art: [],
|
50
|
+
started_previously_on_art: [],
|
51
|
+
completed_new_on_art: [],
|
52
|
+
completed_previously_on_art: []
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def load_patients_into_report(report, patients, tpt, &patient_has_completed_tpt)
|
60
|
+
patients.each do |patient|
|
61
|
+
age_group = patient['age_group']
|
62
|
+
gender = patient['gender']&.first&.upcase || 'Unknown'
|
63
|
+
tpt_states = find_patient_tpt_state(patient, &patient_has_completed_tpt)
|
64
|
+
|
65
|
+
tpt_states.each do |tpt_state|
|
66
|
+
report[age_group][gender][tpt][tpt_state] << patient
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def find_patient_tpt_state(patient, &patient_has_completed_tpt)
|
72
|
+
if patient_has_completed_tpt[patient]
|
73
|
+
return %i[started_new_on_art completed_new_on_art] if patient_new_on_art?(patient)
|
74
|
+
|
75
|
+
return %i[started_previously_on_art completed_previously_on_art]
|
76
|
+
end
|
77
|
+
|
78
|
+
return %i[started_new_on_art] if patient_new_on_art?(patient)
|
79
|
+
|
80
|
+
%i[started_previously_on_art]
|
81
|
+
end
|
82
|
+
|
83
|
+
def patient_new_on_art?(patient)
|
84
|
+
tpt_initiation_date = patient['tpt_initiation_date'].to_date
|
85
|
+
art_start_date = patient['art_start_date'].to_date
|
86
|
+
|
87
|
+
(tpt_initiation_date >= art_start_date) && (tpt_initiation_date < art_start_date + 90.days)
|
88
|
+
end
|
89
|
+
|
90
|
+
def patients_on_tpt
|
91
|
+
ActiveRecord::Base.connection.select_all <<~SQL
|
92
|
+
SELECT person.person_id AS patient_id,
|
93
|
+
patient_identifier.identifier AS arv_number,
|
94
|
+
DATE(MIN(orders.start_date)) AS tpt_initiation_date,
|
95
|
+
date_antiretrovirals_started(person.person_id, MIN(patient_state.start_date)) AS art_start_date,
|
96
|
+
SUM(drug_order.quantity) AS total_pills_taken,
|
97
|
+
SUM(DATEDIFF(orders.auto_expire_date, orders.start_date)) AS total_days_on_medication,
|
98
|
+
person.gender,
|
99
|
+
person.birthdate,
|
100
|
+
disaggregated_age_group(person.birthdate, DATE(#{end_date})) AS age_group,
|
101
|
+
GROUP_CONCAT(DISTINCT orders.concept_id SEPARATOR ',') AS drug_concepts
|
102
|
+
FROM person
|
103
|
+
LEFT JOIN patient_identifier
|
104
|
+
ON patient_identifier.patient_id = person.person_id
|
105
|
+
AND patient_identifier.voided = 0
|
106
|
+
AND patient_identifier.identifier_type IN (SELECT patient_identifier_type_id FROM patient_identifier_type WHERE name = 'ARV Number')
|
107
|
+
INNER JOIN patient_program
|
108
|
+
ON patient_program.patient_id = person.person_id
|
109
|
+
AND patient_program.program_id IN (SELECT program_id FROM program WHERE name = 'HIV PROGRAM')
|
110
|
+
AND patient_program.voided = 0
|
111
|
+
INNER JOIN patient_state
|
112
|
+
ON patient_state.patient_program_id = patient_program.patient_program_id
|
113
|
+
AND patient_state.state = 7 /* State: 7 == On antiretrovirals */
|
114
|
+
AND patient_state.start_date < DATE(#{start_date})
|
115
|
+
AND patient_state.voided = 0
|
116
|
+
INNER JOIN encounter AS prescription_encounter
|
117
|
+
ON prescription_encounter.patient_id = patient_program.patient_id
|
118
|
+
AND prescription_encounter.program_id IN (SELECT program_id FROM program WHERE name = 'HIV PROGRAM')
|
119
|
+
AND prescription_encounter.encounter_type IN (SELECT encounter_type_id FROM encounter_type WHERE name = 'Treatment')
|
120
|
+
AND prescription_encounter.encounter_datetime >= DATE(#{start_date}) - INTERVAL 6 MONTH
|
121
|
+
AND prescription_encounter.encounter_datetime < DATE(#{start_date})
|
122
|
+
AND prescription_encounter.voided = 0
|
123
|
+
INNER JOIN orders
|
124
|
+
ON orders.encounter_id = prescription_encounter.encounter_id
|
125
|
+
AND orders.order_type_id IN (SELECT order_type_id FROM order_type WHERE name = 'Drug order')
|
126
|
+
AND orders.start_date >= DATE(#{start_date}) - INTERVAL 6 MONTH
|
127
|
+
AND orders.start_date < DATE(#{start_date})
|
128
|
+
AND orders.voided = 0
|
129
|
+
INNER JOIN concept_name
|
130
|
+
ON concept_name.concept_id = orders.concept_id
|
131
|
+
AND concept_name.name IN ('Rifapentine', 'Isoniazid', 'Isoniazid/Rifapentine')
|
132
|
+
INNER JOIN drug_order
|
133
|
+
ON drug_order.order_id = orders.order_id
|
134
|
+
AND drug_order.quantity > 0
|
135
|
+
WHERE person.voided = 0
|
136
|
+
AND person.person_id NOT IN (
|
137
|
+
/* People who had a dispensation prior to the 3 to 9 months before start of reporting period.
|
138
|
+
Continuing medication after a 9 months break is considered a restart hence such patients
|
139
|
+
are classified as new on TPT.
|
140
|
+
*/
|
141
|
+
SELECT DISTINCT encounter.patient_id
|
142
|
+
FROM encounter
|
143
|
+
INNER JOIN orders
|
144
|
+
ON orders.encounter_id = encounter.encounter_id
|
145
|
+
AND orders.concept_id IN (SELECT concept_id FROM concept_name WHERE name IN ('Rifapentine', 'Isoniazid') AND voided = 0)
|
146
|
+
AND orders.order_type_id IN (SELECT order_type_id FROM order_type WHERE name = 'Drug order')
|
147
|
+
AND orders.start_date < DATE(#{start_date}) - INTERVAL 6 MONTH
|
148
|
+
AND orders.start_date >= DATE(#{start_date}) - INTERVAL 15 MONTH
|
149
|
+
AND orders.voided = 0
|
150
|
+
INNER JOIN drug_order
|
151
|
+
ON drug_order.order_id = orders.order_id
|
152
|
+
AND drug_order.quantity > 0
|
153
|
+
WHERE encounter.program_id IN (SELECT program_id FROM program WHERE name = 'HIV PROGRAM')
|
154
|
+
AND encounter.encounter_type IN (SELECT encounter_type_id FROM encounter_type WHERE name = 'Treatment')
|
155
|
+
AND encounter.encounter_datetime < DATE(#{start_date}) - INTERVAL 6 MONTH
|
156
|
+
AND encounter.encounter_datetime >= DATE(#{start_date}) - INTERVAL 15 MONTH
|
157
|
+
AND encounter.voided = 0
|
158
|
+
) AND person.person_id NOT IN (
|
159
|
+
/* External consultations */
|
160
|
+
SELECT DISTINCT registration_encounter.patient_id
|
161
|
+
FROM patient_program
|
162
|
+
INNER JOIN program ON program.name = 'HIV PROGRAM'
|
163
|
+
INNER JOIN encounter AS registration_encounter
|
164
|
+
ON registration_encounter.patient_id = patient_program.patient_id
|
165
|
+
AND registration_encounter.program_id = patient_program.program_id
|
166
|
+
AND registration_encounter.encounter_datetime < DATE(#{end_date}) + INTERVAL 1 DAY
|
167
|
+
AND registration_encounter.voided = 0
|
168
|
+
INNER JOIN (
|
169
|
+
SELECT MAX(encounter.encounter_datetime) AS encounter_datetime, encounter.patient_id
|
170
|
+
FROM encounter
|
171
|
+
INNER JOIN encounter_type
|
172
|
+
ON encounter_type.encounter_type_id = encounter.encounter_type
|
173
|
+
AND encounter_type.name = 'Registration'
|
174
|
+
INNER JOIN program
|
175
|
+
ON program.program_id = encounter.program_id
|
176
|
+
AND program.name = 'HIV PROGRAM'
|
177
|
+
WHERE encounter.encounter_datetime < DATE(#{end_date}) AND encounter.voided = 0
|
178
|
+
GROUP BY encounter.patient_id
|
179
|
+
) AS max_registration_encounter
|
180
|
+
ON max_registration_encounter.patient_id = registration_encounter.patient_id
|
181
|
+
AND max_registration_encounter.encounter_datetime = registration_encounter.encounter_datetime
|
182
|
+
INNER JOIN obs AS patient_type_obs
|
183
|
+
ON patient_type_obs.encounter_id = registration_encounter.encounter_id
|
184
|
+
AND patient_type_obs.concept_id IN (SELECT concept_id FROM concept_name WHERE name = 'Type of patient' AND voided = 0)
|
185
|
+
AND patient_type_obs.value_coded IN (SELECT concept_id FROM concept_name WHERE name IN ('Drug refill', 'External consultation') AND voided = 0)
|
186
|
+
AND patient_type_obs.voided = 0
|
187
|
+
WHERE patient_program.voided = 0
|
188
|
+
)
|
189
|
+
GROUP BY person.person_id
|
190
|
+
SQL
|
191
|
+
end
|
192
|
+
|
193
|
+
##
|
194
|
+
# Groups patients into their TPT categories (ie 6H and 3HP) based on their drugs
|
195
|
+
#
|
196
|
+
# Returns an object with a three_hp and six_h methods, each of which
|
197
|
+
# is an array of patients for that category.
|
198
|
+
def group_patients_by_tpt_course(patients)
|
199
|
+
patients.each_with_object(OpenStruct.new(six_h: [], three_hp: [])) do |patient, categories|
|
200
|
+
if patient_on_3hp?(patient)
|
201
|
+
categories.three_hp << patient
|
202
|
+
else
|
203
|
+
categories.six_h << patient
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def patient_on_3hp?(patient)
|
209
|
+
drug_concepts = patient['drug_concepts'].split(',').collect(&:to_i)
|
210
|
+
drug_concepts.intersect?([rifapentine_concept.concept_id, isoniazid_rifapentine_concept&.concept_id])
|
211
|
+
end
|
212
|
+
|
213
|
+
def rifapentine_concept
|
214
|
+
@rifapentine_concept ||= ::ConceptName.find_by!(name: 'Rifapentine')
|
215
|
+
end
|
216
|
+
|
217
|
+
def isoniazid_rifapentine_concept
|
218
|
+
@isoniazid_rifapentine_concept ||= ::ConceptName.find_by!(name: 'Isoniazid/Rifapentine')
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|