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,223 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MalawiHivProgramReports
|
|
4
|
+
module Clinic
|
|
5
|
+
# Generates a hypertension report for a clinic
|
|
6
|
+
# rubocop:disable Metrics/ClassLength
|
|
7
|
+
class HypertensionReport
|
|
8
|
+
AGE_GROUPS = [
|
|
9
|
+
'20-24 years',
|
|
10
|
+
'25-29 years', '30-34 years',
|
|
11
|
+
'35-39 years', '40-44 years',
|
|
12
|
+
'45-49 years', '50-54 years',
|
|
13
|
+
'55-59 years', '60-64 years',
|
|
14
|
+
'65-69 years', '70-74 years',
|
|
15
|
+
'75-79 years', '80-84 years',
|
|
16
|
+
'85-89 years',
|
|
17
|
+
'90 plus years'
|
|
18
|
+
].freeze
|
|
19
|
+
|
|
20
|
+
GENDER = %w[M F].freeze
|
|
21
|
+
|
|
22
|
+
DRUG_MAPPING = {
|
|
23
|
+
'Hydrochlorothiazide (25mg tablet)' => :hydrochlorothiazide_25mg,
|
|
24
|
+
'HCZ (25mg tablet)' => :hydrochlorothiazide_25mg,
|
|
25
|
+
'Hctz (25mg)' => :hydrochlorothiazide_25mg,
|
|
26
|
+
'Amlodipine (5mg tablet)' => :amlodipine_5mg,
|
|
27
|
+
'Amlodipine 5mg' => :amlodipine_5mg,
|
|
28
|
+
'Amlodipine (5mg)' => :amlodipine_5mg,
|
|
29
|
+
'Amlodipine (10mg tablet)' => :amlodipine_10mg,
|
|
30
|
+
'Enalapril (5mg tablet)' => :enalapril_5mg,
|
|
31
|
+
'Enalapril (5mg)' => :enalapril_5mg,
|
|
32
|
+
'Enalapril (10mg tablet)' => :enalapril_10mg,
|
|
33
|
+
'Enalapril (10mg)' => :enalapril_10mg,
|
|
34
|
+
'Atenolol (50mg tablet)' => :atenolol_50mg,
|
|
35
|
+
'Atenolol (50mg)' => :atenolol_50mg,
|
|
36
|
+
'Atenolol (100mg tablet)' => :atenolol_100mg
|
|
37
|
+
}.freeze
|
|
38
|
+
|
|
39
|
+
def initialize(start_date:, end_date:, **_kwargs)
|
|
40
|
+
@start_date = ActiveRecord::Base.connection.quote(start_date)
|
|
41
|
+
@end_date = ActiveRecord::Base.connection.quote(end_date)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def find_report
|
|
45
|
+
@report = init_report
|
|
46
|
+
process_data
|
|
47
|
+
@report
|
|
48
|
+
rescue StandardError => e
|
|
49
|
+
puts e.message
|
|
50
|
+
puts e.backtrace.join("\n")
|
|
51
|
+
raise e
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def init_report
|
|
57
|
+
AGE_GROUPS.each_with_object({}) do |age_group, report|
|
|
58
|
+
report[age_group] = GENDER.each_with_object({}) do |gender, gender_sub_report|
|
|
59
|
+
gender_sub_report[gender] = initialize_gender_metrics
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# rubocop:disable Metrics/MethodLength
|
|
65
|
+
def initialize_gender_metrics
|
|
66
|
+
{
|
|
67
|
+
screened: [],
|
|
68
|
+
normal_reading: [],
|
|
69
|
+
mild_reading: [],
|
|
70
|
+
moderate_reading: [],
|
|
71
|
+
severe_reading: [],
|
|
72
|
+
hydrochlorothiazide_25mg: [],
|
|
73
|
+
amlodipine_5mg: [],
|
|
74
|
+
amlodipine_10mg: [],
|
|
75
|
+
enalapril_5mg: [],
|
|
76
|
+
enalapril_10mg: [],
|
|
77
|
+
atenolol_50mg: [],
|
|
78
|
+
atenolol_100mg: [],
|
|
79
|
+
total_regimen: []
|
|
80
|
+
}
|
|
81
|
+
end
|
|
82
|
+
# rubocop:enable Metrics/MethodLength
|
|
83
|
+
|
|
84
|
+
# rubocop:disable Metrics/AbcSize
|
|
85
|
+
# rubocop:disable Metrics/MethodLength
|
|
86
|
+
def process_data
|
|
87
|
+
(data || []).each do |row|
|
|
88
|
+
age_group = row['age_group']
|
|
89
|
+
gender = row['gender']
|
|
90
|
+
next unless AGE_GROUPS.include?(age_group)
|
|
91
|
+
next unless GENDER.include?(gender)
|
|
92
|
+
|
|
93
|
+
patient = client_info(row)
|
|
94
|
+
cluster = @report[age_group][gender]
|
|
95
|
+
cluster[:screened] << patient
|
|
96
|
+
process_bp_classification(cluster, patient, row['systolic_classification'], row['diastolic_classification'])
|
|
97
|
+
process_drug_data(cluster, patient, row['drug_names'].split(',')) if row['drug_names'].present?
|
|
98
|
+
cluster[:total_regimen] << patient if row['drug_names'].present?
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
# rubocop:enable Metrics/AbcSize
|
|
102
|
+
# rubocop:enable Metrics/MethodLength
|
|
103
|
+
|
|
104
|
+
def process_bp_classification(cluster, patient_id, sys_class, dia_class)
|
|
105
|
+
classification = SEVERITY_ORDER[sys_class.to_sym] > SEVERITY_ORDER[dia_class.to_sym] ? sys_class : dia_class
|
|
106
|
+
case SEVERITY_CLASSIFICATION[classification.to_sym]
|
|
107
|
+
when 'NORMAL'
|
|
108
|
+
cluster[:normal_reading] << patient_id
|
|
109
|
+
when 'MILD'
|
|
110
|
+
cluster[:mild_reading] << patient_id
|
|
111
|
+
when 'MODERATE'
|
|
112
|
+
cluster[:moderate_reading] << patient_id
|
|
113
|
+
when 'SEVERE'
|
|
114
|
+
cluster[:severe_reading] << patient_id
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def process_drug_data(cluster, patient_id, drugs)
|
|
119
|
+
return if drugs.blank?
|
|
120
|
+
|
|
121
|
+
drugs.each do |drug|
|
|
122
|
+
cluster_key = DRUG_MAPPING[drug]
|
|
123
|
+
cluster[cluster_key] << patient_id if cluster_key
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
SEVERITY_ORDER = {
|
|
128
|
+
severe_reading: 4,
|
|
129
|
+
moderate_reading: 3,
|
|
130
|
+
mild_reading: 2,
|
|
131
|
+
normal_reading: 1
|
|
132
|
+
}.freeze
|
|
133
|
+
|
|
134
|
+
SEVERITY_CLASSIFICATION = {
|
|
135
|
+
severe_reading: 'SEVERE',
|
|
136
|
+
moderate_reading: 'MODERATE',
|
|
137
|
+
mild_reading: 'MILD',
|
|
138
|
+
normal_reading: 'NORMAL'
|
|
139
|
+
}.freeze
|
|
140
|
+
|
|
141
|
+
def data
|
|
142
|
+
ActiveRecord::Base.connection.select_all <<~SQL
|
|
143
|
+
SELECT
|
|
144
|
+
tpo.patient_id,
|
|
145
|
+
sys.value_numeric systolic,
|
|
146
|
+
dia.value_numeric diastolic,
|
|
147
|
+
CASE
|
|
148
|
+
WHEN sys.value_numeric <= 139 THEN 'normal_reading'
|
|
149
|
+
WHEN sys.value_numeric > 139 AND sys.value_numeric <= 159 THEN 'mild_reading'
|
|
150
|
+
WHEN sys.value_numeric > 159 AND sys.value_numeric <= 179 THEN 'moderate_reading'
|
|
151
|
+
WHEN sys.value_numeric > 179 THEN 'severe_reading'
|
|
152
|
+
END AS systolic_classification,
|
|
153
|
+
CASE
|
|
154
|
+
WHEN dia.value_numeric <= 89 THEN 'normal_reading'
|
|
155
|
+
WHEN dia.value_numeric > 89 AND dia.value_numeric <= 99 THEN 'mild_reading'
|
|
156
|
+
WHEN dia.value_numeric > 99 AND dia.value_numeric <= 109 THEN 'moderate_reading'
|
|
157
|
+
WHEN dia.value_numeric > 109 THEN 'severe_reading'
|
|
158
|
+
END AS diastolic_classification,
|
|
159
|
+
UPPER(LEFT(p.gender, 1)) gender,
|
|
160
|
+
disaggregated_age_group(p.birthdate, #{@end_date}) age_group,
|
|
161
|
+
i.identifier arv_number,
|
|
162
|
+
latest_drug_order.start_date,
|
|
163
|
+
GROUP_CONCAT(DISTINCT d.name) drug_names
|
|
164
|
+
FROM (
|
|
165
|
+
SELECT MAX(o.obs_datetime) obs_date, o.person_id patient_id
|
|
166
|
+
FROM obs o
|
|
167
|
+
INNER JOIN encounter e ON e.encounter_id = o.encounter_id AND e.voided = 0 AND e.program_id = 1 -- HIV PROGRAM
|
|
168
|
+
WHERE o.concept_id = 5085 -- Systolic blood pressure
|
|
169
|
+
AND o.voided = 0 AND o.obs_datetime >= #{@start_date} AND o.obs_datetime < #{@end_date} + INTERVAL 1 DAY
|
|
170
|
+
GROUP BY o.person_id
|
|
171
|
+
) AS tpo
|
|
172
|
+
INNER JOIN obs sys ON sys.concept_id = 5085 AND sys.person_id = tpo.patient_id AND sys.obs_datetime = tpo.obs_date AND sys.voided = 0
|
|
173
|
+
INNER JOIN obs dia ON dia.concept_id = 5086 AND dia.person_id = tpo.patient_id AND dia.obs_datetime = tpo.obs_date AND dia.voided = 0
|
|
174
|
+
INNER JOIN person p ON p.person_id = tpo.patient_id AND p.voided = 0
|
|
175
|
+
INNER JOIN encounter e ON e.encounter_id = dia.encounter_id AND e.program_id = 1 AND e.voided = 0
|
|
176
|
+
LEFT JOIN patient_identifier i ON i.patient_id = e.patient_id AND i.identifier_type = 4 AND i.voided = 0
|
|
177
|
+
LEFT JOIN (
|
|
178
|
+
SELECT o.patient_id, MAX(o.start_date) start_date
|
|
179
|
+
FROM orders o
|
|
180
|
+
INNER JOIN drug_order dor ON dor.order_id = o.order_id AND dor.quantity > 0
|
|
181
|
+
WHERE o.voided = 0
|
|
182
|
+
AND o.concept_id IN (SELECT concept_id FROM concept_name WHERE name LIKE '%Hydrochlorothiazide%' OR name LIKE '%Amlodipine%' OR name LIKE '%Enalapril%' OR name LIKE '%Atenolol%')
|
|
183
|
+
AND o.start_date >= #{@start_date} AND o.start_date < #{@end_date} + INTERVAL 1 DAY
|
|
184
|
+
GROUP BY o.patient_id
|
|
185
|
+
) AS latest_drug_order ON latest_drug_order.patient_id = tpo.patient_id AND DATE(latest_drug_order.start_date) >= DATE(tpo.obs_date)
|
|
186
|
+
LEFT JOIN orders ord ON ord.start_date = latest_drug_order.start_date AND ord.patient_id = latest_drug_order.patient_id AND ord.voided = 0 AND ord.concept_id IN (SELECT concept_id FROM concept_name WHERE name LIKE '%Hydrochlorothiazide%' OR name LIKE '%Amlodipine%' OR name LIKE '%Enalapril%' OR name LIKE '%Atenolol%')
|
|
187
|
+
LEFT JOIN drug_order dor ON dor.order_id = ord.order_id AND dor.quantity > 0
|
|
188
|
+
LEFT JOIN drug d ON d.drug_id = dor.drug_inventory_id AND d.retired = 0
|
|
189
|
+
WHERE tpo.patient_id NOT IN (#{external_clients})
|
|
190
|
+
GROUP BY tpo.patient_id
|
|
191
|
+
ORDER BY tpo.patient_id ASC
|
|
192
|
+
SQL
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def external_clients
|
|
196
|
+
<<~SQL
|
|
197
|
+
SELECT obs.person_id FROM obs,
|
|
198
|
+
(SELECT person_id, Max(obs_datetime) AS obs_datetime, concept_id FROM obs
|
|
199
|
+
WHERE concept_id IN (SELECT concept_id FROM concept_name WHERE name = 'Type of patient' AND voided = 0)
|
|
200
|
+
AND DATE(obs_datetime) <= #{@end_date}
|
|
201
|
+
AND voided = 0
|
|
202
|
+
GROUP BY person_id) latest_record
|
|
203
|
+
WHERE obs.person_id = latest_record.person_id
|
|
204
|
+
AND obs.concept_id = latest_record.concept_id
|
|
205
|
+
AND obs.obs_datetime = latest_record.obs_datetime
|
|
206
|
+
AND obs.value_coded IN (SELECT concept_id FROM concept_name WHERE name = 'Drug refill' || name = 'External consultation')
|
|
207
|
+
AND obs.voided = 0
|
|
208
|
+
SQL
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def client_info(data)
|
|
212
|
+
{
|
|
213
|
+
patient_id: data['patient_id'],
|
|
214
|
+
arv_number: data['arv_number'],
|
|
215
|
+
gender: data['gender'],
|
|
216
|
+
diastolic: data['diastolic'],
|
|
217
|
+
systolic: data['systolic']
|
|
218
|
+
}
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
# rubocop:enable Metrics/ClassLength
|
|
222
|
+
end
|
|
223
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Cohort report builder class.
|
|
4
|
+
#
|
|
5
|
+
# This class only provides one public method (start_build_report) besides
|
|
6
|
+
# the constructor. This method must be called to build report and save
|
|
7
|
+
# it to database.
|
|
8
|
+
module MalawiHivProgramReports
|
|
9
|
+
module Clinic
|
|
10
|
+
class IptCoverage
|
|
11
|
+
def initialize(start_date:, end_date:)
|
|
12
|
+
@start_date = start_date.to_date.strftime('%Y-%m-%d 00:00:00')
|
|
13
|
+
@end_date = end_date.to_date.strftime('%Y-%m-%d 23:59:59')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def data
|
|
17
|
+
patient_ids = []
|
|
18
|
+
patients = {}
|
|
19
|
+
|
|
20
|
+
(on_art_in_reporting_period || []).each do |data|
|
|
21
|
+
patient_ids << data['patient_id'].to_i
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
return patients if patient_ids.blank?
|
|
25
|
+
|
|
26
|
+
data = ipt_dispensations(patient_ids)
|
|
27
|
+
|
|
28
|
+
(data || []).each do |record|
|
|
29
|
+
patient_id = record['patient_id'].to_i
|
|
30
|
+
age_group = record['age_group']
|
|
31
|
+
gender = record['gender']
|
|
32
|
+
|
|
33
|
+
gender = if gender.blank?
|
|
34
|
+
'Unknown'
|
|
35
|
+
else
|
|
36
|
+
(/F/i.match?(gender) ? 'Female' : 'Male')
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
patients[age_group] = {} if patients[age_group].blank?
|
|
40
|
+
patients[age_group][gender] = {} if patients[age_group][gender].blank?
|
|
41
|
+
patients[age_group][gender][patient_id] = 0 if patients[age_group][gender][patient_id].blank?
|
|
42
|
+
|
|
43
|
+
prescription_info = ActiveRecord::Base.connection.select_one <<-SQL
|
|
44
|
+
SELECT TIMESTAMPDIFF(day, DATE('#{record['start_date']}'), DATE('#{record['auto_expire_date']}')) days;
|
|
45
|
+
SQL
|
|
46
|
+
|
|
47
|
+
next if prescription_info['days'].blank?
|
|
48
|
+
|
|
49
|
+
patients[age_group][gender][patient_id] += prescription_info['days'].to_i
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
age_groups = {}
|
|
53
|
+
|
|
54
|
+
(patients || {}).each do |keys, values|
|
|
55
|
+
pats = values
|
|
56
|
+
(pats || {}).each do |sex, ids|
|
|
57
|
+
(ids || {}).each do |patient_id, count|
|
|
58
|
+
next unless count >= 168
|
|
59
|
+
|
|
60
|
+
age_groups[keys] = {} if age_groups[keys].blank?
|
|
61
|
+
age_groups[keys][sex] = [] if age_groups[keys][sex].blank?
|
|
62
|
+
age_groups[keys][sex] << patient_id
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
age_groups
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def on_art_in_reporting_period
|
|
73
|
+
ActiveRecord::Base.connection.select_all <<-SQL
|
|
74
|
+
select
|
|
75
|
+
`p`.`patient_id` AS `patient_id`, pe.birthdate, pe.gender,
|
|
76
|
+
cast(patient_date_enrolled(`p`.`patient_id`) as date) AS `date_enrolled`
|
|
77
|
+
from
|
|
78
|
+
((`patient_program` `p`
|
|
79
|
+
left join `person` `pe` ON ((`pe`.`person_id` = `p`.`patient_id`))
|
|
80
|
+
left join `patient_state` `s` ON ((`p`.`patient_program_id` = `s`.`patient_program_id`)))
|
|
81
|
+
left join `person` ON ((`person`.`person_id` = `p`.`patient_id`)))
|
|
82
|
+
where
|
|
83
|
+
((`p`.`voided` = 0)
|
|
84
|
+
and (`s`.`voided` = 0)
|
|
85
|
+
and (`p`.`program_id` = 1)
|
|
86
|
+
and (`s`.`state` = 7))
|
|
87
|
+
/*and (DATE(`s`.`start_date`) BETWEEN '#{@start_date}' AND '#{@end_date}')*/
|
|
88
|
+
and pepfar_patient_outcome(p.patient_id, DATE('#{@end_date}')) = 'On antiretrovirals'
|
|
89
|
+
group by `p`.`patient_id`
|
|
90
|
+
HAVING date_enrolled IS NOT NULL;
|
|
91
|
+
SQL
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def ipt_dispensations(patient_ids)
|
|
95
|
+
date_six_months_ago = (@start_date.to_date - 6.months).strftime('%Y-%m-%d 00:00:00')
|
|
96
|
+
|
|
97
|
+
ActiveRecord::Base.connection.select_all <<-SQL
|
|
98
|
+
SELECT
|
|
99
|
+
o.patient_id, birthdate, gender, o.start_date, o.auto_expire_date,
|
|
100
|
+
disaggregated_age_group(p.birthdate, DATE('#{@end_date}')) age_group
|
|
101
|
+
FROM person p
|
|
102
|
+
INNER JOIN orders o ON o.patient_id = p.person_id
|
|
103
|
+
INNER JOIN drug_order t ON o.order_id = t.order_id
|
|
104
|
+
INNER JOIN drug d ON d.drug_id = t.drug_inventory_id
|
|
105
|
+
WHERE o.voided = 0 AND (o.start_date BETWEEN '#{date_six_months_ago}' AND '#{@end_date}')
|
|
106
|
+
AND d.concept_id = 656 AND o.patient_id IN(#{patient_ids.join(',')})
|
|
107
|
+
AND t.quantity > 0 ORDER BY o.patient_id;
|
|
108
|
+
SQL
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MalawiHivProgramReports
|
|
4
|
+
module Clinic
|
|
5
|
+
class IptReport
|
|
6
|
+
def initialize(start_date:, end_date:)
|
|
7
|
+
@start_date = start_date
|
|
8
|
+
@end_date = end_date
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def ipt_coverage
|
|
12
|
+
coverage
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def coverage
|
|
18
|
+
program_id = ::Program.find_by_name('HIV PROGRAM').id
|
|
19
|
+
data = ActiveRecord::Base.connection.select_all <<~SQL
|
|
20
|
+
SELECT patient_id FROM encounter
|
|
21
|
+
WHERE encounter_type = #{::EncounterType.find_by_name('HIV Reception').id}
|
|
22
|
+
AND encounter_datetime BETWEEN '#{@start_date.to_date.strftime('%Y-%m-%d 00:00:00')}'
|
|
23
|
+
AND '#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}' AND voided = 0
|
|
24
|
+
AND program_id = #{program_id}
|
|
25
|
+
GROUP BY DATE(encounter_datetime), patient_id;
|
|
26
|
+
SQL
|
|
27
|
+
|
|
28
|
+
patient_ids = data.map { |p| p['patient_id'].to_i }
|
|
29
|
+
return { patients: 0, prescribed: 0, dispensed: 0 } if patient_ids.blank?
|
|
30
|
+
|
|
31
|
+
ipt_drugs = ::Drug.where('name LIKE (?)', '%Isoniazid%').map(&:drug_id)
|
|
32
|
+
|
|
33
|
+
data = ActiveRecord::Base.connection.select_one <<~SQL
|
|
34
|
+
SELECT count(o.patient_id) AS prescribed FROM orders o
|
|
35
|
+
INNER JOIN encounter e ON e.encounter_id = o.encounter_id
|
|
36
|
+
INNER JOIN drug_order i ON i.order_id = o.order_id
|
|
37
|
+
WHERE o.voided = 0 AND e.patient_id IN(#{patient_ids.join(',')})
|
|
38
|
+
AND start_date BETWEEN "#{@start_date.to_date.strftime('%Y-%m-%d 00:00:00')}"
|
|
39
|
+
AND "#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}"#{' '}
|
|
40
|
+
AND drug_inventory_id IN(#{ipt_drugs.join(',')})#{' '}
|
|
41
|
+
AND e.program_id = #{program_id};
|
|
42
|
+
SQL
|
|
43
|
+
|
|
44
|
+
#
|
|
45
|
+
# data = ::Order.where("start_date BETWEEN ? AND ? AND drug_inventory_id IN(?)
|
|
46
|
+
# AND program_id = ? AND orders.patient_id IN(?)",
|
|
47
|
+
# @start_date.to_date.strftime('%Y-%m-%d 00:00:00'),
|
|
48
|
+
# @end_date.to_date.strftime('%Y-%m-%d 23:59:59'), ipt_drugs, program_id, patient_ids).\
|
|
49
|
+
# joins("INNER JOIN encounter e ON e.encounter_id = orders.encounter_id
|
|
50
|
+
# INNER JOIN drug_order i ON i.order_id = orders.order_id").count(:patient_id)
|
|
51
|
+
|
|
52
|
+
data2 = ActiveRecord::Base.connection.select_one <<~SQL
|
|
53
|
+
SELECT count(o.patient_id) AS dispensed FROM orders o
|
|
54
|
+
INNER JOIN encounter e ON e.encounter_id = o.encounter_id
|
|
55
|
+
INNER JOIN drug_order i ON i.order_id = o.order_id
|
|
56
|
+
WHERE o.voided = 0 AND e.patient_id IN(#{patient_ids.join(',')})
|
|
57
|
+
AND start_date BETWEEN "#{@start_date.to_date.strftime('%Y-%m-%d 00:00:00')}"
|
|
58
|
+
AND "#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}"
|
|
59
|
+
AND drug_inventory_id IN(#{ipt_drugs.join(',')})
|
|
60
|
+
AND quantity > 0 AND e.program_id = #{program_id};
|
|
61
|
+
SQL
|
|
62
|
+
|
|
63
|
+
prescribed = data['prescribed']
|
|
64
|
+
dispensed = data2['dispensed']
|
|
65
|
+
{ patients: patient_ids.length, prescribed:, dispensed: }
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# This class generates the LIMS results report (results delivered electronically)
|
|
4
|
+
module MalawiHivProgramReports
|
|
5
|
+
module Clinic
|
|
6
|
+
class LimsResults
|
|
7
|
+
include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
|
|
8
|
+
attr_reader :start_date, :end_date
|
|
9
|
+
|
|
10
|
+
def initialize(start_date:, end_date:, **kwargs)
|
|
11
|
+
@start_date = start_date
|
|
12
|
+
@end_date = end_date
|
|
13
|
+
@occupation = kwargs[:occupation]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def find_report
|
|
17
|
+
data
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def data
|
|
23
|
+
ActiveRecord::Base.connection.select_all <<~SQL
|
|
24
|
+
SELECT o.start_date AS date_ordered, pn.given_name, pn.family_name, pi.identifier AS arv_number, e.patient_id,
|
|
25
|
+
las.date_received, o.accession_number, CONCAT(COALESCE(res.value_modifier, '='), COALESCE(res.value_text, res.value_numeric)) AS result,
|
|
26
|
+
cn.name AS test_name, las.acknowledgement_type AS result_delivery_mode, statuses.value_text AS order_status, reason_test.name AS test_reason
|
|
27
|
+
FROM orders o
|
|
28
|
+
INNER JOIN lab_lims_order_mappings llom ON llom.order_id = o.order_id
|
|
29
|
+
LEFT JOIN lims_acknowledgement_statuses las ON las.order_id = o.order_id
|
|
30
|
+
INNER JOIN encounter e ON e.encounter_id = o.encounter_id AND e.voided = 0 AND (e.program_id = 1 OR e.program_id = 23) -- HIV PROGRAM AND Laboratory program
|
|
31
|
+
INNER JOIN users u ON u.user_id = o.orderer
|
|
32
|
+
INNER JOIN person_name pn ON pn.person_id = u.person_id
|
|
33
|
+
INNER JOIN obs test ON test.person_id = e.patient_id AND test.voided = 0 AND test.order_id = o.order_id AND test.concept_id = 9737 -- 'Test Type'
|
|
34
|
+
INNER JOIN concept_name cn ON cn.concept_id = test.value_coded AND cn.voided = 0 AND cn.locale_preferred = 1
|
|
35
|
+
LEFT JOIN patient_identifier pi ON pi.patient_id = e.patient_id AND pi.voided = 0 AND pi.identifier_type = #{identifier_type}
|
|
36
|
+
LEFT JOIN obs ON obs.person_id = e.patient_id AND obs.voided = 0 AND obs.order_id = o.order_id
|
|
37
|
+
AND obs.concept_id = 7363 -- 'Lab test result'
|
|
38
|
+
LEFT JOIN obs statuses ON statuses.order_id = o.order_id AND statuses.voided = 0 AND statuses.concept_id = 10682 -- 'lab order status'
|
|
39
|
+
LEFT JOIN obs res ON res.obs_group_id = obs.obs_id AND res.voided = 0 AND res.order_id = o.order_id
|
|
40
|
+
LEFT JOIN obs reason ON reason.order_id = o.order_id AND reason.voided = 0 AND reason.concept_id = 2429 -- 'Reason for test'
|
|
41
|
+
LEFT JOIN concept_name reason_test ON reason_test.concept_id = reason.value_coded AND reason_test.voided = 0
|
|
42
|
+
LEFT JOIN (#{current_occupation_query}) AS a ON a.person_id = e.patient_id
|
|
43
|
+
WHERE DATE(o.start_date) BETWEEN '#{start_date}' AND '#{end_date}' AND o.voided = 0 #{%w[Military Civilian].include?(@occupation) ? 'AND' : ''} #{occupation_filter(occupation: @occupation, field_name: 'value', table_name: 'a', include_clause: false)}
|
|
44
|
+
GROUP BY o.order_id
|
|
45
|
+
SQL
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def identifier_type
|
|
49
|
+
filling_number = ::GlobalPropertyService.use_filing_numbers?
|
|
50
|
+
|
|
51
|
+
filling_number ? 17 : 4
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Outcome List Report
|
|
4
|
+
module MalawiHivProgramReports
|
|
5
|
+
module Clinic
|
|
6
|
+
class OutcomeList
|
|
7
|
+
include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
|
|
8
|
+
|
|
9
|
+
REPORTS = Set.new(%i[transfer_out died stopped]).freeze
|
|
10
|
+
|
|
11
|
+
def initialize(start_date:, end_date:, outcome:, **kwargs)
|
|
12
|
+
@start_date = start_date.to_date.strftime('%Y-%m-%d 00:00:00')
|
|
13
|
+
@end_date = end_date.to_date.strftime('%Y-%m-%d 23:59:59')
|
|
14
|
+
@occupation = kwargs[:occupation]
|
|
15
|
+
@report = load_report outcome.downcase.split.join('_')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def get_list
|
|
19
|
+
@report
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
def start_date
|
|
25
|
+
ActiveRecord::Base.connection.quote(@start_date)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def end_date
|
|
29
|
+
ActiveRecord::Base.connection.quote(@end_date)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def load_report(name)
|
|
33
|
+
name = name.to_sym
|
|
34
|
+
|
|
35
|
+
raise "Invalid report: #{name}" unless REPORTS.include?(name)
|
|
36
|
+
|
|
37
|
+
method(name).call
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def transfer_out
|
|
41
|
+
outcome_query 'Patient transferred out'
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def died
|
|
45
|
+
outcome_query 'Patient died'
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def stopped
|
|
49
|
+
outcome_query 'Treatment stopped'
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def outcome_query(outcome_state)
|
|
53
|
+
report_type = 'moh'
|
|
54
|
+
MalawiHivProgramReports::Moh::CohortBuilder.new(outcomes_definition: report_type).init_temporary_tables(
|
|
55
|
+
@start_date.to_date, @end_date.to_date, @occupation
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
transfer_out_to_location_sql = ''
|
|
59
|
+
transfer_out_to_location_name_sql = ''
|
|
60
|
+
if /Transfer/i.match?(outcome_state)
|
|
61
|
+
concept_id = ::ConceptName.find_by_name('Transfer out to location').concept_id
|
|
62
|
+
transfer_out_to_location_name_sql = ' ,to_location.value_text transferred_out_to'
|
|
63
|
+
transfer_out_to_location_sql = ' LEFT JOIN obs to_location ON to_location.person_id = e.patient_id'
|
|
64
|
+
transfer_out_to_location_sql += " AND to_location.concept_id = #{concept_id} AND to_location.voided = 0"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
data = ActiveRecord::Base.connection.select_all <<~SQL
|
|
68
|
+
SELECT
|
|
69
|
+
e.patient_id, i.identifier, e.birthdate,
|
|
70
|
+
e.gender, n.given_name, n.family_name,
|
|
71
|
+
art_reason.name art_reason, a.value cell_number,
|
|
72
|
+
s3.state_province district, s3.county_district ta,
|
|
73
|
+
s3.city_village village, TIMESTAMPDIFF(year, DATE(e.birthdate), DATE('#{@end_date}')) age,
|
|
74
|
+
pp.date_enrolled, pp.date_completed,
|
|
75
|
+
s2.start_date outcome_date, s2.end_date, s2.state
|
|
76
|
+
#{transfer_out_to_location_name_sql}
|
|
77
|
+
FROM temp_earliest_start_date e
|
|
78
|
+
INNER JOIN temp_patient_outcomes o ON e.patient_id = o.patient_id
|
|
79
|
+
LEFT JOIN patient_identifier i ON i.patient_id = e.patient_id
|
|
80
|
+
AND i.voided = 0 AND i.identifier_type = 4
|
|
81
|
+
INNER JOIN person_name n ON n.person_id = e.patient_id AND n.voided = 0
|
|
82
|
+
LEFT JOIN person_attribute a ON a.person_id = e.patient_id
|
|
83
|
+
AND a.voided = 0 AND a.person_attribute_type_id = 12
|
|
84
|
+
LEFT JOIN person_address s3 ON s3.person_id = e.patient_id
|
|
85
|
+
LEFT JOIN concept_name art_reason ON art_reason.concept_id = e.reason_for_starting_art
|
|
86
|
+
#{transfer_out_to_location_sql}
|
|
87
|
+
INNER JOIN patient_program pp ON pp.patient_id = e.patient_id AND pp.program_id = 1
|
|
88
|
+
INNER JOIN patient_state s2 ON s2.patient_program_id = pp.patient_program_id
|
|
89
|
+
INNER JOIN program_workflow_state ws ON ws.program_workflow_state_id = s2.state
|
|
90
|
+
INNER JOIN program_workflow w ON w.program_workflow_id = ws.program_workflow_id
|
|
91
|
+
INNER JOIN concept_name n2 ON n2.concept_id = ws.concept_id
|
|
92
|
+
WHERE o.cum_outcome = '#{outcome_state}'
|
|
93
|
+
AND pp.voided = 0
|
|
94
|
+
AND s2.voided = 0
|
|
95
|
+
AND s2.start_date
|
|
96
|
+
BETWEEN '#{@start_date}' AND '#{@end_date}' AND s2.state NOT IN(7,1, 12)
|
|
97
|
+
GROUP BY e.patient_id ORDER BY e.patient_id, n.date_created DESC;
|
|
98
|
+
SQL
|
|
99
|
+
|
|
100
|
+
patients = []
|
|
101
|
+
|
|
102
|
+
(data || []).each do |person|
|
|
103
|
+
patients << {
|
|
104
|
+
patient_id: person['patient_id'],
|
|
105
|
+
given_name: person['given_name'],
|
|
106
|
+
family_name: person['family_name'],
|
|
107
|
+
birthdate: person['birthdate'],
|
|
108
|
+
gender: person['gender'],
|
|
109
|
+
arv_number: person['arv_number'],
|
|
110
|
+
outcome: 'Defaulted',
|
|
111
|
+
art_reason: person['art_reason'],
|
|
112
|
+
cell_number: person['cell_number'],
|
|
113
|
+
district: person['district'],
|
|
114
|
+
ta: person['ta'],
|
|
115
|
+
village: person['village'],
|
|
116
|
+
current_age: person['age'],
|
|
117
|
+
identifier: person['identifier'],
|
|
118
|
+
transferred_out_to: person['transferred_out_to'] || 'N/A',
|
|
119
|
+
outcome_date: person['outcome_date']&.to_date || 'N/A'
|
|
120
|
+
}
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
patients
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Returns all patients alive and on treatment within a given
|
|
4
|
+
# time period (Tx Curr).
|
|
5
|
+
module MalawiHivProgramReports
|
|
6
|
+
module Clinic
|
|
7
|
+
class PatientsAliveAndOnTreatment
|
|
8
|
+
attr_reader :start_date, :end_date
|
|
9
|
+
|
|
10
|
+
include MalawiHivProgramReports::Utils::ConcurrencyUtils
|
|
11
|
+
|
|
12
|
+
def initialize(start_date:, end_date:, outcomes_definition: 'moh', rebuild_outcomes: true, **kwargs)
|
|
13
|
+
@start_date = start_date
|
|
14
|
+
@end_date = end_date
|
|
15
|
+
@rebuild_outcomes = rebuild_outcomes
|
|
16
|
+
@outcomes_definition = outcomes_definition
|
|
17
|
+
@occupation = kwargs[:occupation]
|
|
18
|
+
@location = kwargs[:location]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Repopulates temp_patient_outcomes and temp_earliest_start_date tables.
|
|
23
|
+
#
|
|
24
|
+
# The data in temp_patient_outcomes can be used to quickly find patients
|
|
25
|
+
# with various outcomes.
|
|
26
|
+
def refresh_outcomes_table
|
|
27
|
+
logger.debug('Initialising cohort temporary tables...')
|
|
28
|
+
MalawiHivProgramReports::Moh::CohortBuilder.new(outcomes_definition: @outcomes_definition, location:@location)
|
|
29
|
+
.init_temporary_tables(@start_date, @end_date, @occupation)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def find_report
|
|
33
|
+
query.map(&:patient_id)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def query
|
|
37
|
+
with_lock(Cohort::LOCK_FILE) do
|
|
38
|
+
refresh_outcomes_table if @rebuild_outcomes || !outcomes_table_exists?
|
|
39
|
+
|
|
40
|
+
::Patient.find_by_sql <<~SQL
|
|
41
|
+
SELECT patient_id FROM temp_patient_outcomes WHERE cum_outcome LIKE 'On antiretrovirals'
|
|
42
|
+
SQL
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def outcomes_table_exists?
|
|
49
|
+
ActiveRecord::Base.connection.table_exists?(:temp_patient_outcomes)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def logger
|
|
53
|
+
Rails.logger
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MalawiHivProgramReports
|
|
4
|
+
module Clinic
|
|
5
|
+
class PatientsDueForViralLoad
|
|
6
|
+
attr_accessor :start_date, :end_date
|
|
7
|
+
|
|
8
|
+
def initialize(start_date:, end_date:, **_kwargs)
|
|
9
|
+
@start_date = start_date
|
|
10
|
+
@end_date = end_date
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def find_report
|
|
14
|
+
patients_alive_and_on_treatment.select do |patient_id|
|
|
15
|
+
patient_viral_load_due?(patient_id)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def patient_viral_load_due?(patient_id)
|
|
22
|
+
reminder = viral_load_reminder(patient_id)
|
|
23
|
+
reminder[:eligibile] # Yes its spelt eligibile not eligible...
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def patients_alive_and_on_treatment
|
|
27
|
+
PatientsAliveAndOnTreatment
|
|
28
|
+
.new(start_date:, end_date:)
|
|
29
|
+
.find_report
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def viral_load_reminder(patient_id)
|
|
33
|
+
ArtService::VlReminder
|
|
34
|
+
.new(patient_id:, date: end_date)
|
|
35
|
+
.vl_reminder_info
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|