malawi_hiv_program_reports 1.1.16 → 1.1.18

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 394a402d0b5e0c9b210bbf5feff0959854bb2b8d1322ecd97b07107201dcd694
4
- data.tar.gz: 51615fcd686c06088e928b6b10e1bba26a754825e9c3d891e0e7c43c00647098
3
+ metadata.gz: 22a3e944fcb8f4a9e0d862b50321ead38c607a3488c002096735853b06922be5
4
+ data.tar.gz: fb58caba60b6795e915efaadd08fea81e2817129ef500843da95ba48047028aa
5
5
  SHA512:
6
- metadata.gz: aa30a38678f9f6dbeded4d8b3fe6ae920a46c6bea63937906f330cdf50b2cbc1ca6ae95edcf004caa2b4ca5501c6dbf627210f2f496dd49e1ba3964815e3a28f
7
- data.tar.gz: cccee12cb7e4e5d9e5e685230b63309f93ad6db7199de3ae244d461a95e0f825f71a0d82f9b01a535636ad2c45fffc617dfcc68460d1f9508853c059ee71be9e
6
+ metadata.gz: 91ae6c317a44e67d502cc97f3d3d72d54ace89d8def8326f8fc2ea7901e1b4693c0754084b33a1bf9c9d32626dd840b72df1e1c56ee558547ed0ff5056756403
7
+ data.tar.gz: 8d52d72581ab43cc207e34bbb20ecf62d942cd104c854e0890c4b1712346d64a609907cd447c9f912298be40b78d0affd36e4be1a8ef123a86f7fd749258a83b
@@ -29,7 +29,7 @@ module MalawiHivProgramReports
29
29
  rebuild_report if rebuild
30
30
  process_initialization
31
31
  process_data
32
- flatten_and_sort_data
32
+ flatten_the_report
33
33
  rescue StandardError => e
34
34
  Rails.logger.info("Error processing location #{location}: #{e.message}")
35
35
  Rails.logger.info(e.backtrace.join("\n"))
@@ -38,12 +38,12 @@ module MalawiHivProgramReports
38
38
 
39
39
  private
40
40
 
41
- GENDER = %w[M F].freeze
42
- AGGREGATE_GENDER_ROWS = %w[M FP FNP FBf].freeze
41
+ GENDER = %w[Male Female].freeze
42
+ AGGREGATE_GENDER_ROWS = %w[Male FP FNP FBf].freeze
43
43
 
44
44
  def process_initialization
45
45
  init_report
46
- init_aggregate_rows if aggregation
46
+ init_aggregate_rows
47
47
  end
48
48
 
49
49
  def init_report
@@ -85,6 +85,7 @@ module MalawiHivProgramReports
85
85
  regimen = data['regimen'] || 'unknown'
86
86
  regimen = 'unknown' unless COHORT_REGIMENS.include?(regimen)
87
87
  patient_id = data['patient_id']
88
+ maternal_status = data['maternal_status']
88
89
  # we need to handle regimes that only have one P to become PP. Otherwise if it is already PP or PA we leave
89
90
  # it as is. Regimens are in this format NUMBERLETTERS
90
91
  regimen = regimen.gsub(/(\d+[A-Za-z]*P)\z/, '\1P') if regimen.match?(/\A\d+[A-Za-z]*[^P]P\z/)
@@ -92,63 +93,65 @@ module MalawiHivProgramReports
92
93
  report[age_group.to_s][gender.to_s][regimen.to_s] << patient_id
93
94
  report[age_group.to_s][gender.to_s]['tx_curr'] << patient_id
94
95
  report[age_group.to_s][gender.to_s]['total'] << patient_id
95
- process_aggregate_rows(gender:, regimen:, patient_id:) if aggregation
96
+ process_aggregate_rows(gender:, regimen:, patient_id:, maternal_status:)
96
97
  end
97
98
  end
98
99
 
99
- def process_maternal_data
100
- result = MalawiHivProgramReports::Pepfar::ViralLoadCoverage2.new(start_date:, end_date:, location:)
101
- .vl_maternal_status(maternal.keys)
102
- # result comes in this form: { FP: [], FBf: [] }
103
- # we need to loop through the keys
104
- result.each_key do |key|
105
- result[key].each do |patient_id|
106
- report['All'][key.to_s]['tx_curr'] << patient_id
107
- report['All'][key.to_s][maternal[patient_id].to_s] << patient_id
108
- report['All'][key.to_s]['total'] << patient_id
109
- end
110
- end
111
- end
112
- # rubocop:enable Metrics/AbcSize
113
-
114
- def process_aggregate_rows(gender:, regimen:, patient_id:)
100
+ def process_aggregate_rows(gender:, regimen:, patient_id:, maternal_status: nil)
115
101
  if gender == 'M'
116
102
  report['All']['M']['tx_curr'] << patient_id
117
103
  report['All']['M'][regimen.to_s] << patient_id
118
104
  report['All']['M']['total'] << patient_id
119
- else
120
- maternal[patient_id] = regimen
105
+ return
121
106
  end
122
- end
123
107
 
124
- # we need to flatten the data
125
- def flatten_data
126
- flat_data = []
127
- report.each do |age_group, gender_data|
128
- gender_data.each do |gender, regimen_data|
129
- flat_data << {
130
- 'age_group' => age_group,
131
- 'gender' => gender,
132
- **regimen_data
133
- }
134
- end
108
+ case maternal_status
109
+ when 'FP'
110
+ report['All']['FP']['tx_curr'] << patient_id
111
+ report['All']['FP'][regimen.to_s] << patient_id
112
+ report['All']['FP']['total'] << patient_id
113
+ when 'FBf'
114
+ report['All']['FBf']['tx_curr'] << patient_id
115
+ report['All']['FBf'][regimen.to_s] << patient_id
116
+ report['All']['FBf']['total'] << patient_id
117
+ else
118
+ report['All']['FNP']['tx_curr'] << patient_id
119
+ report['All']['FNP'][regimen.to_s] << patient_id
120
+ report['All']['FNP']['total'] << patient_id
135
121
  end
136
- flat_data
137
122
  end
123
+ # rubocop:enable Metrics/AbcSize
138
124
 
139
- def flatten_and_sort_data
140
- flat_data = flatten_data
125
+ def flatten_the_report
126
+ result = []
127
+ @report.each do |age_group, age_group_report|
128
+ next if age_group == 'Unknown'
141
129
 
142
- age_group_order = ['Unknown', '<1 year', '1-4 years', '5-9 years', '10-14 years', '15-19 years',
143
- '20-24 years', '25-29 years', '30-34 years', '35-39 years', '40-44 years', '45-49 years',
144
- '50-54 years', '55-59 years', '60-64 years', '65-69 years', '70-74 years', '75-79 years',
145
- '80-84 years', '85-89 years', '90 plus years', 'All']
146
- gender_order = %w[F M FP FNP FBF]
130
+ age_group_report.each_key do |gender|
131
+ next if gender == 'Unknown'
147
132
 
148
- flat_data.sort_by do |row|
149
- [age_group_order.index(row['age_group']) || Float::INFINITY,
150
- gender_order.index(row['gender']) || Float::INFINITY]
133
+ result << process_age_group_report(age_group, gender, age_group_report[gender])
134
+ end
151
135
  end
136
+
137
+ new_group = pepfar_age_groups.map { |age_group| age_group }
138
+ gender_scores = { 'Female' => 0, 'Male' => 1, 'FNP' => 3, 'FP' => 2, 'FBf' => 4 }
139
+ result_scores = result.sort_by do |item|
140
+ gender_score = gender_scores[item[:gender]] || 999
141
+ age_group_score = new_group.index(item[:age_group]) || 999
142
+ [gender_score, age_group_score]
143
+ end
144
+ # remove all unknown age groups
145
+ result_scores.reject { |item| item[:age_group].match?(/unknown/i) }
146
+ end
147
+
148
+ def process_age_group_report(age_group, gender, age_group_report)
149
+ {
150
+ age_group:,
151
+ gender:,
152
+ # spread the age group report
153
+ **age_group_report
154
+ }
152
155
  end
153
156
 
154
157
  # rubocop:disable Metrics/MethodLength
@@ -158,16 +161,23 @@ module MalawiHivProgramReports
158
161
  prescriptions.patient_id,
159
162
  COALESCE(regimens.name, 'unknown') AS regimen,
160
163
  prescriptions.age_group,
161
- prescriptions.gender
164
+ CASE prescriptions.gender
165
+ WHEN 'M' THEN 'Male'
166
+ WHEN 'F' THEN 'Female'
167
+ ELSE 'Unknown'
168
+ END gender,
169
+ prescriptions.maternal_status
162
170
  FROM (
163
171
  SELECT
164
172
  tcm.patient_id,
165
173
  GROUP_CONCAT(DISTINCT(tcm.drug_id) ORDER BY tcm.drug_id ASC) AS drugs,
166
174
  disaggregated_age_group(date(earliest_start_date.birthdate), date('#{end_date}')) AS age_group,
175
+ c.maternal_status,
167
176
  earliest_start_date.gender
168
177
  FROM cdr_temp_current_medication #{current_partition} tcm
169
178
  INNER JOIN cdr_temp_patient_outcomes #{current_partition} AS outcomes ON outcomes.patient_id = tcm.patient_id AND outcomes.cum_outcome = 'On antiretrovirals'
170
179
  INNER JOIN cdr_temp_cohort_members #{current_partition} AS earliest_start_date ON earliest_start_date.patient_id = tcm.patient_id AND earliest_start_date.gender IN ('M','F')
180
+ LEFT JOIN cdr_temp_maternal_status #{current_partition} c ON c.patient_id = tcm.patient_id
171
181
  GROUP BY tcm.patient_id
172
182
  ) AS prescriptions
173
183
  LEFT JOIN (
@@ -1,4 +1,6 @@
1
1
  # frozen_string_literal: true
2
+ #
3
+ require 'redis'
2
4
 
3
5
  module MalawiHivProgramReports
4
6
  module Moh
@@ -21,10 +23,7 @@ module MalawiHivProgramReports
21
23
  @definition = kwargs[:definition] || 'pepfar'
22
24
  @iteration = Concurrent::AtomicFixnum.new(0)
23
25
  @start_time = Time.now
24
- @redis = Redis.new
25
- @redis.set('cumulative_progress', 0)
26
- @redis.set('cumulative_status', 'running')
27
- @redis.set('cumulative_time_taken', 0)
26
+ prepare_redis
28
27
  definition = @definition.downcase
29
28
  raise ArgumentError, "Invalid outcomes definition: #{definition}" unless %w[moh pepfar].include?(definition)
30
29
  end
@@ -59,7 +58,7 @@ module MalawiHivProgramReports
59
58
  queue = Queue.new
60
59
  locations.each { |loc| queue << loc }
61
60
 
62
- threads = Array.new(65) do
61
+ threads = Array.new(configured_threads) do
63
62
  Thread.new do
64
63
  until queue.empty?
65
64
  loc = begin
@@ -86,6 +85,22 @@ module MalawiHivProgramReports
86
85
  end
87
86
  # rubocop:enable Metrics/MethodLength, Metrics/AbcSize
88
87
 
88
+ def configured_threads
89
+ threads = ::Utils::ConfigReader.application_read('max_threads')&.to_i
90
+ Rails::logger.info("Max threads: #{threads}")
91
+ threads.positive? ? threads : 60
92
+ rescue StandardError => e
93
+ Rails.logger.info("Error reading max threads: #{e.message}")
94
+ 60
95
+ end
96
+
97
+ def prepare_redis
98
+ @redis = Redis.new
99
+ @redis.set('cumulative_progress', 0)
100
+ @redis.set('cumulative_status', 'running')
101
+ @redis.set('cumulative_time_taken', 0)
102
+ end
103
+
89
104
  # update progress in redis and channel
90
105
  def update_progress(current_iteration, total)
91
106
  percentage = (current_iteration.to_f / total * 100).round(2)
@@ -161,13 +176,22 @@ module MalawiHivProgramReports
161
176
  outcome = MalawiHivProgramReports::Moh::CumulativeOutcome.new(end_date:, location:, definition:, rebuild:,
162
177
  start_date:)
163
178
  rebuild ? outcome.find_report : outcome.update_outcomes_by_definition
179
+ other_dependables(location)
164
180
  end_time = Time.now
165
181
  time_taken = ((end_time - start_time) / 60).round(2)
166
182
  save_completed_site(location:, time_taken:)
167
183
  end
168
184
 
185
+ def other_dependables(loc)
186
+ Pepfar::TxTb.new(start_date:, end_date:, location: loc, occupation: nil).process_tb_data
187
+ Pepfar::MaternalStatus.new(start_date:, end_date:, location: loc).process_data
188
+ end
189
+
169
190
  # rubocop:disable Metrics/CyclomaticComplexity
170
191
  def prepare_tables
192
+ create_cdr_temp_maternal_status unless check_if_table_exists('cdr_temp_maternal_status')
193
+ create_cdr_tb_confirmed_and_on_treatment unless check_if_table_exists('cdr_tb_confirmed_and_on_treatment')
194
+ create_tb_screened unless check_if_table_exists('cdr_tb_screened')
171
195
  create_cdr_other_patient_types unless check_if_table_exists('cdr_other_patient_types')
172
196
  create_temp_potential_cohort_members_table unless check_if_table_exists('cdr_temp_potential_cohort_members')
173
197
  create_min_drug_orders_table unless check_if_table_exists('cdr_temp_min_drug_orders')
@@ -193,6 +217,18 @@ module MalawiHivProgramReports
193
217
  SQL
194
218
  end
195
219
 
220
+ def create_cdr_temp_maternal_status
221
+ ActiveRecord::Base.connection.execute <<~SQL
222
+ CREATE TABLE IF NOT EXISTS cdr_temp_maternal_status (
223
+ patient_id INT(11) NOT NULL,
224
+ site_id INT(11) NOT NULL,
225
+ maternal_status VARCHAR(5) DEFAULT NULL,
226
+ PRIMARY KEY (patient_id, site_id)
227
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
228
+ PARTITION BY LIST(site_id) (#{partition_by_site})
229
+ SQL
230
+ end
231
+
196
232
  def create_cdr_temp_cohort_status
197
233
  ActiveRecord::Base.connection.execute <<~SQL
198
234
  CREATE TABLE IF NOT EXISTS cdr_temp_cohort_status (
@@ -303,6 +339,40 @@ module MalawiHivProgramReports
303
339
  SQL
304
340
  end
305
341
 
342
+ def create_cdr_tb_confirmed_and_on_treatment
343
+ ActiveRecord::Base.connection.execute <<~SQL
344
+ CREATE TABLE IF NOT EXISTS cdr_tb_confirmed_and_on_treatment (
345
+ patient_id INT(11) NOT NULL,
346
+ site_id INT(11) NOT NULL,
347
+ gender VARCHAR(50) DEFAULT NULL,
348
+ age_group VARCHAR(50) DEFAULT NULL,
349
+ tb_confirmed_date DATE DEFAULT NULL,
350
+ has_tb_confirmed_date BOOLEAN DEFAULT NULL,
351
+ enrollment_date DATE DEFAULT NULL,
352
+ prev_reading DATE DEFAULT NULL,
353
+ PRIMARY KEY (patient_id, site_id)
354
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
355
+ PARTITION BY LIST(site_id) (#{partition_by_site})
356
+ SQL
357
+ end
358
+
359
+ def create_tb_screened
360
+ ActiveRecord::Base.connection.execute <<~SQL
361
+ CREATE TABLE IF NOT EXISTS cdr_tb_screened (
362
+ patient_id INT(11) NOT NULL,
363
+ site_id INT(11) NOT NULL,
364
+ gender VARCHAR(255) NULL,
365
+ screened_date DATE NULL,
366
+ enrollment_date DATE NULL,
367
+ age_group VARCHAR(255) NULL,
368
+ tb_status VARCHAR(255) NULL,
369
+ screening_methods VARCHAR(255) NULL,
370
+ PRIMARY KEY (patient_id, site_id)
371
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
372
+ PARTITION BY LIST(site_id) (#{partition_by_site})
373
+ SQL
374
+ end
375
+
306
376
  def create_cdr_other_patient_types
307
377
  ActiveRecord::Base.connection.execute <<~SQL
308
378
  CREATE TABLE IF NOT EXISTS cdr_other_patient_types (
@@ -485,6 +555,8 @@ module MalawiHivProgramReports
485
555
  def clear_tables
486
556
  # if locations is empty then we truncating otherwise we clear the locations
487
557
  if locations.empty?
558
+ ActiveRecord::Base.connection.execute('TRUNCATE TABLE cdr_tb_confirmed_and_on_treatment')
559
+ ActiveRecord::Base.connection.execute('TRUNCATE TABLE cdr_tb_screened')
488
560
  ActiveRecord::Base.connection.execute('TRUNCATE TABLE cdr_other_patient_types')
489
561
  ActiveRecord::Base.connection.execute('TRUNCATE TABLE cdr_temp_potential_cohort_members')
490
562
  ActiveRecord::Base.connection.execute('TRUNCATE TABLE cdr_temp_min_drug_orders')
@@ -493,6 +565,8 @@ module MalawiHivProgramReports
493
565
  ActiveRecord::Base.connection.execute('TRUNCATE TABLE cdr_temp_external_clients')
494
566
  ActiveRecord::Base.connection.execute('TRUNCATE TABLE cdr_reason_for_starting_art')
495
567
  else
568
+ ActiveRecord::Base.connection.execute("DELETE FROM cdr_tb_confirmed_and_on_treatment WHERE site_id IN (#{locations.join(',')})")
569
+ ActiveRecord::Base.connection.execute("DELETE FROM cdr_tb_screened WHERE site_id IN (#{locations.join(',')})")
496
570
  ActiveRecord::Base.connection.execute("DELETE FROM cdr_other_patient_types WHERE site_id IN (#{locations.join(',')})")
497
571
  ActiveRecord::Base.connection.execute("DELETE FROM cdr_temp_potential_cohort_members WHERE site_id IN (#{locations.join(',')})")
498
572
  ActiveRecord::Base.connection.execute("DELETE FROM cdr_temp_min_drug_orders WHERE site_id IN (#{locations.join(',')})")
@@ -22,7 +22,109 @@ module MalawiHivProgramReports
22
22
  end
23
23
 
24
24
  def find_report
25
- MalawiHivProgramReports::Pepfar::ViralLoadCoverage2.new(tx_curr_definition: 'pepfar', start_date: @start_date, end_date: @end_date, location: @location).vl_maternal_status(@patient_ids)
25
+ vl_maternal_status
26
+ end
27
+
28
+ def process_data
29
+ clear_maternal_status
30
+ load_pregnant_women
31
+ load_breast_feeding
32
+ end
33
+
34
+ private
35
+
36
+ def vl_maternal_status(patient_list)
37
+ return { FP: [], FBf: [] } if patient_list.blank?
38
+
39
+ pregnant = pregnant_women(patient_list).map { |woman| woman['person_id'].to_i }
40
+ return { FP: pregnant, FBf: [] } if (patient_list - pregnant).blank?
41
+
42
+ feeding = breast_feeding(patient_list - pregnant).map { |woman| woman['person_id'].to_i }
43
+
44
+ {
45
+ FP: pregnant,
46
+ FBf: feeding
47
+ }
48
+ end
49
+
50
+ def pregnant_women(patient_list)
51
+ ActiveRecord::Base.connection.execute <<~SQL
52
+ SELECT person_id
53
+ FROM cdr_temp_maternal_status #{current_partition}
54
+ WHERE maternal_status = 'FP' AND patient_od IN (#{patient_list.join(',')})
55
+ SQL
56
+ end
57
+
58
+ def breast_feeding(patient_list)
59
+ ActiveRecord::Base.connection.execute <<~SQL
60
+ SELECT person_id
61
+ FROM cdr_temp_maternal_status #{current_partition}
62
+ WHERE maternal_status = 'FBf' AND patient_id IN (#{patient_list.join(',')})
63
+ SQL
64
+ end
65
+
66
+ def load_pregnant_women
67
+ ActiveRecord::Base.connection.execute <<~SQL
68
+ INSERT INTO cdr_temp_maternal_status #{current_partition} (patient_id, site_id, maternal_status)
69
+ SELECT o.person_id, #{location}, 'FP' as maternal_status
70
+ FROM obs #{current_partition} o
71
+ INNER JOIN cdr_temp_cohort_members #{current_partition} c ON c.patient_id = o.person_id AND c.gender = 'F'
72
+ LEFT JOIN obs #{current_partition} a ON a.person_id = o.person_id AND a.obs_datetime > o.obs_datetime AND a.concept_id IN (#{pregnant_concepts.to_sql}) AND a.voided = 0
73
+ AND a.obs_datetime >= DATE(#{ActiveRecord::Base.connection.quote(start_date)}) AND a.obs_datetime < DATE(#{ActiveRecord::Base.connection.quote(end_date)}) + INTERVAL 1 DAY
74
+ WHERE a.obs_id is null
75
+ AND o.obs_datetime >= DATE(#{ActiveRecord::Base.connection.quote(start_date)})
76
+ AND o.obs_datetime < DATE(#{ActiveRecord::Base.connection.quote(end_date)}) + INTERVAL 1 DAY
77
+ AND o.voided = 0
78
+ AND o.concept_id in (#{pregnant_concepts.to_sql})
79
+ AND o.value_coded IN (#{yes_concepts.join(',')})
80
+ GROUP BY o.person_id
81
+ SQL
82
+ end
83
+
84
+ def load_breast_feeding
85
+ ActiveRecord::Base.connection.execute <<~SQL
86
+ INSERT INTO cdr_temp_maternal_status #{current_partition} (patient_id, site_id, maternal_status)
87
+ SELECT o.person_id, #{location}, 'FBf' as maternal_status
88
+ FROM obs #{current_partition} o
89
+ INNER JOIN cdr_temp_cohort_members #{current_partition} c ON c.patient_id = o.person_id AND c.gender = 'F'
90
+ LEFT JOIN obs #{current_partition} a ON a.person_id = o.person_id AND a.obs_datetime > o.obs_datetime AND a.concept_id IN (#{breast_feeding_concepts.to_sql}) AND a.voided = 0
91
+ AND a.obs_datetime >= DATE(#{ActiveRecord::Base.connection.quote(start_date)}) AND a.obs_datetime < DATE(#{ActiveRecord::Base.connection.quote(end_date)}) + INTERVAL 1 DAY
92
+ WHERE a.obs_id is null
93
+ AND o.obs_datetime >= DATE(#{ActiveRecord::Base.connection.quote(start_date)})
94
+ AND o.obs_datetime < DATE(#{ActiveRecord::Base.connection.quote(end_date)}) + INTERVAL 1 DAY
95
+ AND o.voided = 0
96
+ AND o.concept_id IN (#{breast_feeding_concepts.to_sql})
97
+ AND o.value_coded IN (#{yes_concepts.join(',')})
98
+ AND o.person_id NOT IN (SELECT c.patient_id FROM cdr_temp_maternal_status #{current_partition} c)
99
+ GROUP BY o.person_id
100
+ SQL
101
+ end
102
+
103
+ def clear_maternal_status
104
+ ActiveRecord::Base.connection.execute <<~SQL
105
+ DELETE FROM cdr_temp_maternal_status where site_id = #{location}
106
+ SQL
107
+ end
108
+
109
+ def yes_concepts
110
+ @yes_concepts ||= ::ConceptName.where(name: 'Yes').select(:concept_id).map do |record|
111
+ record['concept_id'].to_i
112
+ end
113
+ end
114
+
115
+ def pregnant_concepts
116
+ @pregnant_concepts ||= ::ConceptName.where(name: ['Is patient pregnant?', 'patient pregnant'])
117
+ .select(:concept_id)
118
+ end
119
+
120
+ def breast_feeding_concepts
121
+ @breast_feeding_concepts ||= ::ConceptName.where(name: ['Breast feeding?', 'Breast feeding', 'Breastfeeding'])
122
+ .select(:concept_id)
123
+ end
124
+
125
+ def encounter_types
126
+ @encounter_types ||= ::EncounterType.where(name: ['HIV CLINIC CONSULTATION', 'HIV STAGING'])
127
+ .select(:encounter_type_id)
26
128
  end
27
129
  end
28
130
  end