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