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,172 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
##
|
4
|
+
# TB Preventive Therapy indicators for ART cohort
|
5
|
+
module MalawiHivProgramReports
|
6
|
+
module Cohort
|
7
|
+
class Tpt
|
8
|
+
include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
|
9
|
+
include MalawiHivProgramReports::Adapters::Moh::Custom
|
10
|
+
|
11
|
+
def initialize(start_date:, end_date:, **kwargs)
|
12
|
+
@start_date = start_date
|
13
|
+
@end_date = end_date
|
14
|
+
@occupation = kwargs[:occupation]
|
15
|
+
@location = kwargs[:location]
|
16
|
+
@adapter = ActiveRecord::Base.connection.adapter_name.downcase
|
17
|
+
return unless @occupation.present?
|
18
|
+
|
19
|
+
process_occupation(start_date: @start_date, end_date: @end_date,
|
20
|
+
occupation: @occupation, location: @location)
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Patients (re-)initiated on 3HP in current reporting period.
|
25
|
+
#
|
26
|
+
# Candidates for this indicator are patients who either have
|
27
|
+
# had their first dispensation in the current reporting period
|
28
|
+
# or patients who have restarted 3HP in the current reporting
|
29
|
+
# period after breaking from the course for a period of at least
|
30
|
+
# 9 months (3 quarters).
|
31
|
+
def newly_initiated_on_3hp
|
32
|
+
# newly_initiated_on_tpt(start_date, end_date).each_with_object([]) do |patient, patients|
|
33
|
+
# patients << patient['patient_id'] unless patient_on_3hp?(patient)
|
34
|
+
# end
|
35
|
+
processed_tpt_clients.select { |patient| patient_on_3hp?(patient) }
|
36
|
+
end
|
37
|
+
|
38
|
+
##
|
39
|
+
# Patients (re-)initiated on IPT in current reporting period
|
40
|
+
#
|
41
|
+
# Has a similar definition to 3HP, please refer to 3HP docs
|
42
|
+
# above.
|
43
|
+
def newly_initiated_on_ipt
|
44
|
+
# newly_initiated_on_tpt(start_date, end_date).each_with_object([]) do |patient, patients|
|
45
|
+
# patients << patient['patient_id'] if patient_on_3hp?(patient)
|
46
|
+
# end
|
47
|
+
processed_tpt_clients.reject { |patient| patient_on_3hp?(patient) }
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def patient_on_3hp?(patient)
|
53
|
+
drug_concepts = patient['drug_concepts'].split(',').collect(&:to_i)
|
54
|
+
drug_concepts.intersect?([rifapentine_concept.concept_id, three_hp_concept&.concept_id])
|
55
|
+
end
|
56
|
+
|
57
|
+
def rifapentine_concept
|
58
|
+
@rifapentine_concept ||= ::ConceptName.find_by!(name: 'Rifapentine')
|
59
|
+
end
|
60
|
+
|
61
|
+
def three_hp_concept
|
62
|
+
@three_hp_concept ||= ::ConceptName.find_by!(name: 'Isoniazid/Rifapentine')
|
63
|
+
end
|
64
|
+
|
65
|
+
def processed_tpt_clients
|
66
|
+
@processed_tpt_clients ||= process_tpt_clients
|
67
|
+
end
|
68
|
+
|
69
|
+
def process_tpt_clients
|
70
|
+
patients = []
|
71
|
+
newly_initiated_on_tpt.each do |patient|
|
72
|
+
course = patient['course'].include?('3HP') ? '3HP' : 'IPT'
|
73
|
+
next if patient['last_course'].present?
|
74
|
+
if patient['tpt_initial_start_date'].present? && patient['tpt_initial_start_date'].to_date < @start_date.to_date
|
75
|
+
next
|
76
|
+
end
|
77
|
+
|
78
|
+
if patient['transfer_course'].blank? && patient['last_course'].blank?
|
79
|
+
patients << patient
|
80
|
+
elsif patient['transfer_course'].present? && patient['last_course'].blank?
|
81
|
+
patients << patient if patient['months_since_tpt_transfer'].to_i >= (course == '3HP' ? 1 : 2)
|
82
|
+
elsif patient['transfer_course'].blank? && patient['last_course'].present?
|
83
|
+
patients << patient if patient['months_since_last_tpt'].to_i >= (course == '3HP' ? 1 : 2)
|
84
|
+
elsif patient['last_tpt_end_date'].to_date >= patient['transfer_end_date'].to_date
|
85
|
+
patients << patient if patient['months_since_last_tpt'].to_i >= (course == '3HP' ? 1 : 2)
|
86
|
+
elsif patient['last_tpt_end_date'].to_date < patient['transfer_end_date'].to_date
|
87
|
+
patients << patient if patient['months_since_tpt_transfer'].to_i >= (course == '3HP' ? 1 : 2)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
patients
|
91
|
+
end
|
92
|
+
|
93
|
+
def newly_initiated_on_tpt
|
94
|
+
start_date = ActiveRecord::Base.connection.quote(@start_date)
|
95
|
+
end_date = ActiveRecord::Base.connection.quote(@end_date)
|
96
|
+
|
97
|
+
@newly_initiated_on_tpt ||= ActiveRecord::Base.connection.select_all <<~SQL
|
98
|
+
SELECT
|
99
|
+
cohort_patients.patient_id,
|
100
|
+
cohort_patients.earliest_start_date,
|
101
|
+
MIN(orders.start_date )as tpt_start_date,
|
102
|
+
#{@adapter == 'mysql2' ? "GROUP_CONCAT(DISTINCT orders.concept_id SEPARATOR ',') AS drug_concepts," : "(SELECT STRING_AGG(concept_id::VARCHAR, ',') FROM (SELECT DISTINCT orders.concept_id FROM orders) AS subquery) AS drug_concepts,"}
|
103
|
+
CASE
|
104
|
+
WHEN count(distinct(orders.concept_id)) > 1 THEN '3HP old'
|
105
|
+
WHEN orders.concept_id = #{three_hp_concept.concept_id} THEN '3HP new'
|
106
|
+
ELSE '6H'
|
107
|
+
END AS course,
|
108
|
+
tpt_transfer_in_obs.value_datetime AS tpt_initial_start_date,
|
109
|
+
CASE
|
110
|
+
WHEN tpt_transfer_in_obs.concept_id IS NULL THEN NULL
|
111
|
+
WHEN count(distinct(tpt_transfer_in_obs.concept_id)) > 1 THEN '3HP old'
|
112
|
+
WHEN tpt_transfer_in_obs.concept_id = #{three_hp_concept.concept_id} THEN '3HP new'
|
113
|
+
ELSE '6H'
|
114
|
+
END AS transfer_course,
|
115
|
+
tpt_transfer_in_obs.obs_datetime AS transfer_end_date,
|
116
|
+
tpt_transfer_in_obs.value_numeric AS transfer_amount,
|
117
|
+
#{@adapter == 'mysql2' ? 'TIMESTAMPDIFF(MONTH, tpt_transfer_in_obs.obs_datetime, MIN(orders.start_date)) AS months_since_tpt_transfer,' : '(EXTRACT(YEAR FROM AGE(MIN(orders.start_date), tpt_transfer_in_obs.obs_datetime)) * 12 + EXTRACT(MONTH FROM AGE(MIN(orders.start_date), tpt_transfer_in_obs.obs_datetime))) AS months_since_tpt_transfer,'}
|
118
|
+
#{@adapter == 'mysql2' ? 'TIMESTAMPDIFF(MONTH, last_tpt_prescription.auto_expire_date, MIN(orders.start_date)) AS months_since_last_tpt,' : '(EXTRACT(YEAR FROM AGE(MIN(orders.start_date), last_tpt_prescription.auto_expire_date)) * 12 + EXTRACT(MONTH FROM AGE(MIN(orders.start_date), last_tpt_prescription.auto_expire_date))) AS months_since_last_tpt,'}
|
119
|
+
last_tpt_prescription.course AS last_course,
|
120
|
+
last_tpt_prescription.start_date AS last_tpt_start_date,
|
121
|
+
last_tpt_prescription.auto_expire_date AS last_tpt_end_date
|
122
|
+
FROM temp_earliest_start_date AS cohort_patients
|
123
|
+
INNER JOIN orders
|
124
|
+
ON orders.patient_id = cohort_patients.patient_id
|
125
|
+
AND orders.order_type_id = (SELECT order_type_id FROM order_type WHERE name = 'Drug order' LIMIT 1)
|
126
|
+
AND orders.start_date >= #{start_date}
|
127
|
+
AND orders.start_date < #{interval_manager(date: end_date, value: 1, interval: 'DAY', operator: '+')}
|
128
|
+
AND orders.voided = 0 #{site_manager(operator: 'AND', column: 'orders.site_id', location: @location)}
|
129
|
+
INNER JOIN concept_name AS tpt_drug_concepts
|
130
|
+
ON tpt_drug_concepts.concept_id = orders.concept_id
|
131
|
+
AND tpt_drug_concepts.name IN ('Rifapentine', 'Isoniazid', 'Isoniazid/Rifapentine')
|
132
|
+
AND tpt_drug_concepts.voided = 0
|
133
|
+
INNER JOIN drug_order AS drug_orders
|
134
|
+
ON drug_orders.order_id = orders.order_id
|
135
|
+
AND drug_orders.quantity > 0 #{site_manager(operator: 'AND', column: 'drug_orders.site_id', location: @location)}
|
136
|
+
INNER JOIN encounter
|
137
|
+
/* Ensure we are dealing with ART prescriptions (Treatment encounter) */
|
138
|
+
ON encounter.encounter_id = orders.encounter_id
|
139
|
+
AND encounter.encounter_type = (SELECT encounter_type_id FROM encounter_type WHERE name = 'Treatment' LIMIT 1)
|
140
|
+
AND encounter.program_id = (SELECT program_id FROM program WHERE name = 'HIV PROGRAM' LIMIT 1)
|
141
|
+
AND encounter.voided = 0
|
142
|
+
LEFT JOIN obs tpt_transfer_in_obs
|
143
|
+
ON tpt_transfer_in_obs.person_id = orders.patient_id
|
144
|
+
AND tpt_transfer_in_obs.concept_id = #{::ConceptName.find_by_name('TPT Drugs Received').concept_id}
|
145
|
+
AND tpt_transfer_in_obs.voided = 0 #{site_manager(operator: 'AND', column: 'tpt_transfer_in_obs.site_id', location: @location)}
|
146
|
+
AND tpt_transfer_in_obs.value_drug IN (SELECT drug_id FROM drug WHERE concept_id IN (SELECT concept_id FROM concept_name WHERE name IN ('Rifapentine', 'Isoniazid', 'Isoniazid/Rifapentine')))
|
147
|
+
-- Get the last TPT prescription
|
148
|
+
LEFT JOIN (
|
149
|
+
SELECT
|
150
|
+
o.patient_id,
|
151
|
+
MAX(o.start_date) AS start_date,
|
152
|
+
MAX(o.auto_expire_date) AS auto_expire_date,
|
153
|
+
CASE
|
154
|
+
WHEN count(distinct(o.concept_id)) > 1 THEN '3HP old'
|
155
|
+
WHEN o.concept_id = #{three_hp_concept.concept_id} THEN '3HP new'
|
156
|
+
ELSE '6H'
|
157
|
+
END AS course
|
158
|
+
FROM temp_earliest_start_date
|
159
|
+
INNER JOIN orders o ON o.patient_id = temp_earliest_start_date.patient_id #{site_manager(operator: 'AND', column: 'o.site_id', location: @location)}
|
160
|
+
INNER JOIN concept_name AS tpt_drug_concepts ON tpt_drug_concepts.concept_id = o.concept_id AND tpt_drug_concepts.name IN ('Rifapentine', 'Isoniazid', 'Isoniazid/Rifapentine') AND tpt_drug_concepts.voided = 0
|
161
|
+
INNER JOIN drug_order AS drug_orders ON drug_orders.order_id = o.order_id AND drug_orders.quantity > 0 #{site_manager(operator: 'AND', column: 'drug_orders.site_id', location: @location)}
|
162
|
+
INNER JOIN encounter ON encounter.encounter_id = o.encounter_id AND encounter.encounter_type = (SELECT encounter_type_id FROM encounter_type WHERE LOWER(name) = Lower('Treatment') LIMIT 1) AND encounter.program_id = (SELECT program_id FROM program WHERE LOWER(name) = LOWER('HIV Program') LIMIT 1) AND encounter.voided = 0
|
163
|
+
WHERE o.voided = 0 #{site_manager(operator: 'AND', column: 'temp_earliest_start_date.site_id', location: @location)}
|
164
|
+
AND o.start_date < #{start_date}
|
165
|
+
GROUP BY o.patient_id, o.concept_id
|
166
|
+
) AS last_tpt_prescription ON last_tpt_prescription.patient_id = orders.patient_id
|
167
|
+
GROUP BY cohort_patients.patient_id #{@adapter == 'mysql2' ? '' : ', tpt_transfer_in_obs.concept_id, tpt_transfer_in_obs.obs_datetime, tpt_transfer_in_obs.value_numeric, last_tpt_prescription.course, last_tpt_prescription.start_date, last_tpt_prescription.auto_expire_date, orders.concept_id, tpt_transfer_in_obs.value_datetime, tpt_transfer_in_obs.concept_id'}
|
168
|
+
SQL
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
@@ -0,0 +1,278 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MalawiHivProgramReports
|
4
|
+
module Moh
|
5
|
+
# Cohort report builder class.
|
6
|
+
#
|
7
|
+
# This class only provides one public method (start_build_report) besides
|
8
|
+
# the constructor. This method must be called to build report and save
|
9
|
+
# it to database.
|
10
|
+
# rubocop:disable Metrics/ClassLength
|
11
|
+
class Cohort
|
12
|
+
include MalawiHivProgramReports::Utils::ConcurrencyUtils
|
13
|
+
include MalawiHivProgramReports::Utils::ModelUtils
|
14
|
+
include MalawiHivProgramReports::Adapters::Moh::Custom
|
15
|
+
|
16
|
+
LOCK_FILE = 'art_service/reports/cohort.lock'
|
17
|
+
|
18
|
+
def initialize(name:, type:, start_date:, end_date:, **kwargs)
|
19
|
+
@name = name
|
20
|
+
@start_date = start_date
|
21
|
+
@end_date = end_date
|
22
|
+
@type = type
|
23
|
+
@occupation = kwargs[:occupation]
|
24
|
+
@location = kwargs[:location]
|
25
|
+
@cohort_builder = CohortBuilder.new(outcomes_definition: 'moh', **kwargs)
|
26
|
+
@cohort_struct = CohortStruct.new
|
27
|
+
@location_name = @location && Location.find(@location).name
|
28
|
+
end
|
29
|
+
|
30
|
+
def build_report
|
31
|
+
with_lock(LOCK_FILE, blocking: false) do
|
32
|
+
init_drill_down_table
|
33
|
+
clear_drill_down
|
34
|
+
@cohort_builder.build(@cohort_struct, @start_date, @end_date, @occupation)
|
35
|
+
save_report
|
36
|
+
end
|
37
|
+
rescue ::FailedToAcquireLock => e
|
38
|
+
Rails.logger.warn("ART#Cohort report is locked by another process: #{e}")
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_report
|
42
|
+
::Report.where(type: @type, name: "#{@name}#{"-#{@occupation}"}#{"-#{@location_name}"}",
|
43
|
+
start_date: @start_date, end_date: @end_date)
|
44
|
+
.order(date_created: :desc)
|
45
|
+
.first
|
46
|
+
end
|
47
|
+
|
48
|
+
def defaulter_list(pepfar)
|
49
|
+
report_type = (pepfar ? 'pepfar' : 'moh')
|
50
|
+
defaulter_date_sql = pepfar ? 'current_pepfar_defaulter_date' : 'current_defaulter_date'
|
51
|
+
CohortBuilder.new(outcomes_definition: report_type, location: @location)
|
52
|
+
.init_temporary_tables(@start_date, @end_date, @occupation)
|
53
|
+
|
54
|
+
data = ActiveRecord::Base.connection.select_all <<~SQL
|
55
|
+
SELECT
|
56
|
+
e.patient_id, i.identifier arv_number, e.birthdate,
|
57
|
+
e.gender, n.given_name, n.family_name,
|
58
|
+
art_reason.name art_reason, a.value cell_number, landmark.value landmark,
|
59
|
+
s.state_province district, s.county_district ta,
|
60
|
+
s.city_village village, TIMESTAMPDIFF(year, DATE(e.birthdate), DATE('#{@end_date}')) age,
|
61
|
+
#{defaulter_date_sql}(e.patient_id, TIMESTAMP('#{@end_date.to_date.strftime('%Y-%m-%d 23:59:59')}')) AS defaulter_date,
|
62
|
+
appointment.appointment_date AS appointment_date
|
63
|
+
FROM temp_earliest_start_date e
|
64
|
+
INNER JOIN temp_patient_outcomes o ON e.patient_id = o.patient_id
|
65
|
+
INNER JOIN (
|
66
|
+
SELECT e.patient_id, MAX(o.value_datetime) appointment_date
|
67
|
+
FROM encounter e
|
68
|
+
INNER JOIN obs o ON o.encounter_id = e.encounter_id AND o.voided = 0 AND o.concept_id = 5096 -- appointment date
|
69
|
+
WHERE e.encounter_type = 7 -- appointment encounter type
|
70
|
+
AND e.program_id = 1 -- HIV PROGRAM
|
71
|
+
AND e.patient_id IN (SELECT patient_id FROM temp_patient_outcomes WHERE cum_outcome = 'Defaulted')
|
72
|
+
AND e.encounter_datetime < DATE('#{@end_date}') + INTERVAL 1 DAY
|
73
|
+
GROUP BY e.patient_id
|
74
|
+
) appointment ON appointment.patient_id = e.patient_id
|
75
|
+
LEFT JOIN patient_identifier i ON i.patient_id = e.patient_id
|
76
|
+
AND i.voided = 0 AND i.identifier_type = 4
|
77
|
+
INNER JOIN person_name n ON n.person_id = e.patient_id AND n.voided = 0
|
78
|
+
LEFT JOIN person_attribute a ON a.person_id = e.patient_id
|
79
|
+
AND a.voided = 0 AND a.person_attribute_type_id = 12
|
80
|
+
LEFT JOIN person_attribute landmark ON landmark.person_id = e.patient_id AND landmark.voided = 0 AND landmark.person_attribute_type_id = 19
|
81
|
+
LEFT JOIN person_address s ON s.person_id = e.patient_id AND s.voided = 0
|
82
|
+
LEFT JOIN concept_name art_reason ON art_reason.concept_id = e.reason_for_starting_art AND art_reason.voided = 0
|
83
|
+
WHERE o.cum_outcome = 'Defaulted' GROUP BY e.patient_id
|
84
|
+
ORDER BY e.patient_id, n.date_created DESC;
|
85
|
+
SQL
|
86
|
+
|
87
|
+
patients = []
|
88
|
+
|
89
|
+
(data || []).each do |person|
|
90
|
+
defaulter_date = person['defaulter_date']&.to_date || 'N/A'
|
91
|
+
|
92
|
+
unless defaulter_date == 'N/A'
|
93
|
+
next if defaulter_date < @start_date.to_date
|
94
|
+
next if defaulter_date > @end_date.to_date
|
95
|
+
end
|
96
|
+
|
97
|
+
patients << {
|
98
|
+
person_id: person['patient_id'],
|
99
|
+
given_name: person['given_name'],
|
100
|
+
family_name: person['family_name'],
|
101
|
+
birthdate: person['birthdate'],
|
102
|
+
gender: person['gender'],
|
103
|
+
arv_number: person['arv_number'],
|
104
|
+
outcome: 'Defaulted',
|
105
|
+
defaulter_date:,
|
106
|
+
appointment_date: person['appointment_date'].to_date.strftime('%Y-%m-%d'),
|
107
|
+
art_reason: person['art_reason'],
|
108
|
+
cell_number: person['cell_number'],
|
109
|
+
district: person['district'],
|
110
|
+
ta: person['ta'],
|
111
|
+
village: person['village'],
|
112
|
+
current_age: person['age'],
|
113
|
+
landmark: person['landmark']
|
114
|
+
}
|
115
|
+
end
|
116
|
+
|
117
|
+
patients
|
118
|
+
end
|
119
|
+
|
120
|
+
def cohort_report_drill_down(id)
|
121
|
+
id = ActiveRecord::Base.connection.quote(id)
|
122
|
+
|
123
|
+
patients = ActiveRecord::Base.connection.select_all <<~SQL
|
124
|
+
SELECT i.identifier arv_number, p.birthdate,
|
125
|
+
p.gender, n.given_name, n.family_name, p.person_id patient_id,
|
126
|
+
outcomes.cum_outcome AS outcome
|
127
|
+
FROM person p
|
128
|
+
INNER JOIN cohort_drill_down c ON c.patient_id = p.person_id
|
129
|
+
INNER JOIN temp_patient_outcomes AS outcomes
|
130
|
+
ON outcomes.patient_id = c.patient_id
|
131
|
+
LEFT JOIN patient_identifier i ON i.patient_id = p.person_id
|
132
|
+
AND i.voided = 0 AND i.identifier_type = 4
|
133
|
+
LEFT JOIN person_name n ON n.person_id = p.person_id AND n.voided = 0
|
134
|
+
WHERE c.reporting_report_design_resource_id = #{id}
|
135
|
+
GROUP BY p.person_id ORDER BY p.person_id, p.date_created;
|
136
|
+
SQL
|
137
|
+
|
138
|
+
patients.map do |person|
|
139
|
+
{
|
140
|
+
person_id: person['patient_id'],
|
141
|
+
given_name: person['given_name'],
|
142
|
+
family_name: person['family_name'],
|
143
|
+
birthdate: person['birthdate'],
|
144
|
+
gender: person['gender'],
|
145
|
+
arv_number: person['arv_number'],
|
146
|
+
outcome: person['outcome']
|
147
|
+
}
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
private
|
152
|
+
|
153
|
+
LOGGER = Rails.logger
|
154
|
+
|
155
|
+
# Writes the report to database
|
156
|
+
def save_report
|
157
|
+
::Report.transaction do
|
158
|
+
report = ::Report.create(name: "#{@name}#{"-#{@occupation}"}#{"-#{@location_name}"}",
|
159
|
+
start_date: @start_date,
|
160
|
+
end_date: @end_date,
|
161
|
+
type: @type,
|
162
|
+
creator: ::User.current.id,
|
163
|
+
renderer_type: 'PDF')
|
164
|
+
values = save_report_values(report)
|
165
|
+
|
166
|
+
{ report:, values: }
|
167
|
+
end
|
168
|
+
rescue StandardError => e
|
169
|
+
LOGGER.error("Failed to save report: #{e}")
|
170
|
+
end
|
171
|
+
|
172
|
+
# Writes the report values to database
|
173
|
+
def save_report_values(report)
|
174
|
+
@cohort_struct.values.collect do |value|
|
175
|
+
puts "Saving #{value.name} = #{value_contents_to_json(value.contents)}"
|
176
|
+
report_value = ::ReportValue.create(report:,
|
177
|
+
name: value.name,
|
178
|
+
indicator_name: value.indicator_name,
|
179
|
+
indicator_short_name: value.indicator_short_name,
|
180
|
+
creator: ::User.current.id,
|
181
|
+
description: value.description,
|
182
|
+
contents: value_contents_to_json(value.contents))
|
183
|
+
|
184
|
+
raise "Failed to save report value: #{report_value.errors.as_json}" unless report_value.errors.empty?
|
185
|
+
|
186
|
+
save_patients(report_value, value_contents_to_json(value).contents)
|
187
|
+
|
188
|
+
report_value
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def init_drill_down_table
|
193
|
+
exe_create_drill_down_table(adapter: ActiveRecord::Base.connection.adapter_name.downcase)
|
194
|
+
end
|
195
|
+
|
196
|
+
def clear_drill_down
|
197
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
198
|
+
DELETE FROM cohort_drill_down #{site_manager(operator: 'WHERE', column: 'site_id', location: @location)}
|
199
|
+
SQL
|
200
|
+
rescue StandardError => e
|
201
|
+
ActiveRecord::Base.connection.execute 'DROP TABLE IF EXISTS cohort_drill_down;'
|
202
|
+
LOGGER.error("Failed to clear drill down table: #{e}")
|
203
|
+
raise e
|
204
|
+
end
|
205
|
+
|
206
|
+
def value_contents_to_json(value_contents)
|
207
|
+
if value_contents.respond_to?(:each) && !value_contents.is_a?(String)
|
208
|
+
if value_contents.respond_to?(:length)
|
209
|
+
value_contents.length
|
210
|
+
elsif value_contents.respond_to?(:size)
|
211
|
+
value_contents.size
|
212
|
+
else
|
213
|
+
value_contents
|
214
|
+
end
|
215
|
+
else
|
216
|
+
value_contents
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
PATIENT_ID_KEYS = ['patient_id', :patient_id, 'person_id', :person_id].freeze
|
221
|
+
|
222
|
+
def save_patients(r, values)
|
223
|
+
return if values.blank? || !values.respond_to?(:each)
|
224
|
+
|
225
|
+
get_patient_id = lambda do |patient, keys = PATIENT_ID_KEYS|
|
226
|
+
break nil if keys.empty?
|
227
|
+
|
228
|
+
patient[keys.first] || get_patient_id[patient, keys[1..keys.size]]
|
229
|
+
end
|
230
|
+
|
231
|
+
patient_ids = values.map do |patient|
|
232
|
+
if patient.respond_to?(:key?) && PATIENT_ID_KEYS.any? { |key| patient.key?(key) }
|
233
|
+
get_patient_id[patient]
|
234
|
+
elsif patient.respond_to?(:each) && patient.respond_to?(:first)
|
235
|
+
patient.first
|
236
|
+
else
|
237
|
+
patient
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
sql_insert_statement = nil
|
242
|
+
patient_ids.select do |patient_id|
|
243
|
+
if sql_insert_statement.blank?
|
244
|
+
sql_insert_statement = "(#{r.id}, #{patient_id}, #{@location})"
|
245
|
+
else
|
246
|
+
sql_insert_statement += ",(#{r.id}, #{patient_id}, #{@location})"
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
return if sql_insert_statement.blank?
|
251
|
+
|
252
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
253
|
+
INSERT INTO cohort_drill_down (reporting_report_design_resource_id, patient_id, site_id)
|
254
|
+
VALUES #{sql_insert_statement};
|
255
|
+
SQL
|
256
|
+
rescue StandardError => e
|
257
|
+
LOGGER.error("Failed to save patients: #{e}")
|
258
|
+
raise e
|
259
|
+
end
|
260
|
+
|
261
|
+
def calculate_age(birthdate)
|
262
|
+
birthdate = begin
|
263
|
+
birthdate.to_date
|
264
|
+
rescue StandardError
|
265
|
+
nil
|
266
|
+
end
|
267
|
+
return 'N/A' if birthdate.blank?
|
268
|
+
|
269
|
+
birthdate = ActiveRecord::Base.connection.select_one <<~SQL
|
270
|
+
SELECT #{timestampdiff_manager(date1: DATE(birthdate), date2: DATE(@end_date), interval: 'YEAR')} age;
|
271
|
+
SQL
|
272
|
+
|
273
|
+
birthdate['age']
|
274
|
+
end
|
275
|
+
end
|
276
|
+
# rubocop:enable Metrics/ClassLength
|
277
|
+
end
|
278
|
+
end
|