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,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
|