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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/app/services/malawi_hiv_program_reports/README.md +16 -0
  3. data/app/services/malawi_hiv_program_reports/adapters/moh/custom.rb +199 -0
  4. data/app/services/malawi_hiv_program_reports/archiving_candidates.rb +130 -0
  5. data/app/services/malawi_hiv_program_reports/arv_refill_periods.rb +311 -0
  6. data/app/services/malawi_hiv_program_reports/clinic/README.md +5 -0
  7. data/app/services/malawi_hiv_program_reports/clinic/appointments_report.rb +317 -0
  8. data/app/services/malawi_hiv_program_reports/clinic/discrepancy_report.rb +42 -0
  9. data/app/services/malawi_hiv_program_reports/clinic/docs/hypertension_report.md +31 -0
  10. data/app/services/malawi_hiv_program_reports/clinic/drug_dispensations.rb +48 -0
  11. data/app/services/malawi_hiv_program_reports/clinic/external_consultation_clients.rb +69 -0
  12. data/app/services/malawi_hiv_program_reports/clinic/hypertension_report.rb +223 -0
  13. data/app/services/malawi_hiv_program_reports/clinic/ipt_coverage.rb +112 -0
  14. data/app/services/malawi_hiv_program_reports/clinic/ipt_report.rb +69 -0
  15. data/app/services/malawi_hiv_program_reports/clinic/lims_results.rb +55 -0
  16. data/app/services/malawi_hiv_program_reports/clinic/outcome_list.rb +127 -0
  17. data/app/services/malawi_hiv_program_reports/clinic/patients_alive_and_on_treatment.rb +57 -0
  18. data/app/services/malawi_hiv_program_reports/clinic/patients_due_for_viral_load.rb +39 -0
  19. data/app/services/malawi_hiv_program_reports/clinic/patients_on_antiretrovirals.rb +44 -0
  20. data/app/services/malawi_hiv_program_reports/clinic/patients_on_dtg.rb +36 -0
  21. data/app/services/malawi_hiv_program_reports/clinic/patients_on_treatment.rb +42 -0
  22. data/app/services/malawi_hiv_program_reports/clinic/patients_with_outdated_demographics.rb +173 -0
  23. data/app/services/malawi_hiv_program_reports/clinic/pregnant_patients.rb +91 -0
  24. data/app/services/malawi_hiv_program_reports/clinic/regimen_dispensation_data.rb +282 -0
  25. data/app/services/malawi_hiv_program_reports/clinic/regimen_switch.rb +456 -0
  26. data/app/services/malawi_hiv_program_reports/clinic/regimens_and_formulations.rb +182 -0
  27. data/app/services/malawi_hiv_program_reports/clinic/regimens_by_weight_and_gender.rb +108 -0
  28. data/app/services/malawi_hiv_program_reports/clinic/retention.rb +246 -0
  29. data/app/services/malawi_hiv_program_reports/clinic/stock_card_report.rb +65 -0
  30. data/app/services/malawi_hiv_program_reports/clinic/tpt_outcome.rb +494 -0
  31. data/app/services/malawi_hiv_program_reports/clinic/tx_rtt.rb +169 -0
  32. data/app/services/malawi_hiv_program_reports/clinic/viral_load.rb +292 -0
  33. data/app/services/malawi_hiv_program_reports/clinic/viral_load_disaggregated.rb +97 -0
  34. data/app/services/malawi_hiv_program_reports/clinic/viral_load_results.rb +175 -0
  35. data/app/services/malawi_hiv_program_reports/clinic/visits_report.rb +113 -0
  36. data/app/services/malawi_hiv_program_reports/clinic/vl_collection.rb +48 -0
  37. data/app/services/malawi_hiv_program_reports/cohort/outcomes.rb +338 -0
  38. data/app/services/malawi_hiv_program_reports/cohort/regimens.rb +69 -0
  39. data/app/services/malawi_hiv_program_reports/cohort/side_effects.rb +141 -0
  40. data/app/services/malawi_hiv_program_reports/cohort/tpt.rb +172 -0
  41. data/app/services/malawi_hiv_program_reports/moh/cohort.rb +278 -0
  42. data/app/services/malawi_hiv_program_reports/moh/cohort_builder.rb +2337 -0
  43. data/app/services/malawi_hiv_program_reports/moh/cohort_disaggregated.rb +608 -0
  44. data/app/services/malawi_hiv_program_reports/moh/cohort_disaggregated_additions.rb +208 -0
  45. data/app/services/malawi_hiv_program_reports/moh/cohort_disaggregated_builder.rb +526 -0
  46. data/app/services/malawi_hiv_program_reports/moh/cohort_struct.rb +219 -0
  47. data/app/services/malawi_hiv_program_reports/moh/cohort_survival_analysis.rb +203 -0
  48. data/app/services/malawi_hiv_program_reports/moh/moh_tpt.rb +223 -0
  49. data/app/services/malawi_hiv_program_reports/moh/tpt_newly_initiated.rb +235 -0
  50. data/app/services/malawi_hiv_program_reports/pepfar/defaulter_list.rb +25 -0
  51. data/app/services/malawi_hiv_program_reports/pepfar/maternal_status.rb +29 -0
  52. data/app/services/malawi_hiv_program_reports/pepfar/patient_start_vl.rb +45 -0
  53. data/app/services/malawi_hiv_program_reports/pepfar/regimen_switch.rb +479 -0
  54. data/app/services/malawi_hiv_program_reports/pepfar/sc_arvdisp.rb +174 -0
  55. data/app/services/malawi_hiv_program_reports/pepfar/sc_curr.rb +98 -0
  56. data/app/services/malawi_hiv_program_reports/pepfar/tb_prev.rb +163 -0
  57. data/app/services/malawi_hiv_program_reports/pepfar/tb_prev2.rb +222 -0
  58. data/app/services/malawi_hiv_program_reports/pepfar/tb_prev3.rb +421 -0
  59. data/app/services/malawi_hiv_program_reports/pepfar/tpt_status.rb +181 -0
  60. data/app/services/malawi_hiv_program_reports/pepfar/tx_ml.rb +181 -0
  61. data/app/services/malawi_hiv_program_reports/pepfar/tx_new.rb +205 -0
  62. data/app/services/malawi_hiv_program_reports/pepfar/tx_rtt.rb +288 -0
  63. data/app/services/malawi_hiv_program_reports/pepfar/tx_tb.rb +283 -0
  64. data/app/services/malawi_hiv_program_reports/pepfar/utils.rb +141 -0
  65. data/app/services/malawi_hiv_program_reports/pepfar/viral_load_coverage.rb +414 -0
  66. data/app/services/malawi_hiv_program_reports/pepfar/viral_load_coverage2.rb +433 -0
  67. data/app/services/malawi_hiv_program_reports/report_map.rb +56 -0
  68. data/app/services/malawi_hiv_program_reports/utils/README.md +8 -0
  69. data/app/services/malawi_hiv_program_reports/utils/common_sql_query_utils.rb +60 -0
  70. data/app/services/malawi_hiv_program_reports/utils/concurrency_utils.rb +53 -0
  71. data/app/services/malawi_hiv_program_reports/utils/docs/common_sql_query_utils.md +53 -0
  72. data/app/services/malawi_hiv_program_reports/utils/model_utils.rb +66 -0
  73. data/app/services/malawi_hiv_program_reports/utils/parameter_utils.rb +32 -0
  74. data/app/services/malawi_hiv_program_reports/utils/time_utils.rb +52 -0
  75. data/lib/malawi_hiv_program_reports/version.rb +1 -1
  76. 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