malawi_hiv_program_reports 1.1.16 → 1.1.17

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 394a402d0b5e0c9b210bbf5feff0959854bb2b8d1322ecd97b07107201dcd694
4
- data.tar.gz: 51615fcd686c06088e928b6b10e1bba26a754825e9c3d891e0e7c43c00647098
3
+ metadata.gz: c818eb00df67da231594873c08c20bb5df18a2c53433788a96e27a7ca1211bf9
4
+ data.tar.gz: fd99e8c2ca139a33ddd76a9eefbaef20dfcf6eee7c1b6290919e2240c39b9c63
5
5
  SHA512:
6
- metadata.gz: aa30a38678f9f6dbeded4d8b3fe6ae920a46c6bea63937906f330cdf50b2cbc1ca6ae95edcf004caa2b4ca5501c6dbf627210f2f496dd49e1ba3964815e3a28f
7
- data.tar.gz: cccee12cb7e4e5d9e5e685230b63309f93ad6db7199de3ae244d461a95e0f825f71a0d82f9b01a535636ad2c45fffc617dfcc68460d1f9508853c059ee71be9e
6
+ metadata.gz: f61982a9e3ce30521079abacdd89144d6c03d10fde293d5fbefcb61fac57a143e5680f5216eee22e8423f37d8b88014205f602003af0644a7a95078fed7d052c
7
+ data.tar.gz: ea196faf6b0a471e995e4885058acbc0a141c7fcbe948e502801648ed796fbea5e72bf1d0f28b556ea0ccd1404cbc78046928ebb5330acdcfd7068f54f16c924
@@ -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
@@ -72,7 +74,7 @@ module MalawiHivProgramReports
72
74
  ActiveRecord::Base.connection_pool.with_connection do
73
75
  process_data loc
74
76
  current_iteration = iteration.increment
75
- update_progress(current_iteration, locations.size)
77
+ # update_progress(current_iteration, locations.size)
76
78
  rescue StandardError => e
77
79
  Rails.logger.info("Error processing location #{loc}: #{e.message}")
78
80
  Rails.logger.info(e.backtrace.join("\n"))
@@ -161,13 +163,22 @@ module MalawiHivProgramReports
161
163
  outcome = MalawiHivProgramReports::Moh::CumulativeOutcome.new(end_date:, location:, definition:, rebuild:,
162
164
  start_date:)
163
165
  rebuild ? outcome.find_report : outcome.update_outcomes_by_definition
166
+ other_dependables(location)
164
167
  end_time = Time.now
165
168
  time_taken = ((end_time - start_time) / 60).round(2)
166
169
  save_completed_site(location:, time_taken:)
167
170
  end
168
171
 
172
+ def other_dependables(loc)
173
+ Pepfar::TxTb.new(start_date:, end_date:, location: loc, occupation: nil).process_tb_data
174
+ Pepfar::MaternalStatus.new(start_date:, end_date:, location: loc).process_data
175
+ end
176
+
169
177
  # rubocop:disable Metrics/CyclomaticComplexity
170
178
  def prepare_tables
179
+ create_cdr_temp_maternal_status unless check_if_table_exists('cdr_temp_maternal_status')
180
+ create_cdr_tb_confirmed_and_on_treatment unless check_if_table_exists('cdr_tb_confirmed_and_on_treatment')
181
+ create_tb_screened unless check_if_table_exists('cdr_tb_screened')
171
182
  create_cdr_other_patient_types unless check_if_table_exists('cdr_other_patient_types')
172
183
  create_temp_potential_cohort_members_table unless check_if_table_exists('cdr_temp_potential_cohort_members')
173
184
  create_min_drug_orders_table unless check_if_table_exists('cdr_temp_min_drug_orders')
@@ -193,6 +204,18 @@ module MalawiHivProgramReports
193
204
  SQL
194
205
  end
195
206
 
207
+ def create_cdr_temp_maternal_status
208
+ ActiveRecord::Base.connection.execute <<~SQL
209
+ CREATE TABLE IF NOT EXISTS cdr_temp_maternal_status (
210
+ patient_id INT(11) NOT NULL,
211
+ site_id INT(11) NOT NULL,
212
+ maternal_status VARCHAR(5) DEFAULT NULL,
213
+ PRIMARY KEY (patient_id, site_id)
214
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
215
+ PARTITION BY LIST(site_id) (#{partition_by_site})
216
+ SQL
217
+ end
218
+
196
219
  def create_cdr_temp_cohort_status
197
220
  ActiveRecord::Base.connection.execute <<~SQL
198
221
  CREATE TABLE IF NOT EXISTS cdr_temp_cohort_status (
@@ -303,6 +326,40 @@ module MalawiHivProgramReports
303
326
  SQL
304
327
  end
305
328
 
329
+ def create_cdr_tb_confirmed_and_on_treatment
330
+ ActiveRecord::Base.connection.execute <<~SQL
331
+ CREATE TABLE IF NOT EXISTS cdr_tb_confirmed_and_on_treatment (
332
+ patient_id INT(11) NOT NULL,
333
+ site_id INT(11) NOT NULL,
334
+ gender VARCHAR(50) DEFAULT NULL,
335
+ age_group VARCHAR(50) DEFAULT NULL,
336
+ tb_confirmed_date DATE DEFAULT NULL,
337
+ has_tb_confirmed_date BOOLEAN DEFAULT NULL,
338
+ enrollment_date DATE DEFAULT NULL,
339
+ prev_reading DATE DEFAULT NULL,
340
+ PRIMARY KEY (patient_id, site_id)
341
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
342
+ PARTITION BY LIST(site_id) (#{partition_by_site})
343
+ SQL
344
+ end
345
+
346
+ def create_tb_screened
347
+ ActiveRecord::Base.connection.execute <<~SQL
348
+ CREATE TABLE IF NOT EXISTS cdr_tb_screened (
349
+ patient_id INT(11) NOT NULL,
350
+ site_id INT(11) NOT NULL,
351
+ gender VARCHAR(255) NULL,
352
+ screened_date DATE NULL,
353
+ enrollment_date DATE NULL,
354
+ age_group VARCHAR(255) NULL,
355
+ tb_status VARCHAR(255) NULL,
356
+ screening_methods VARCHAR(255) NULL,
357
+ PRIMARY KEY (patient_id, site_id)
358
+ ) ENGINE=InnoDB DEFAULT CHARSET=utf8
359
+ PARTITION BY LIST(site_id) (#{partition_by_site})
360
+ SQL
361
+ end
362
+
306
363
  def create_cdr_other_patient_types
307
364
  ActiveRecord::Base.connection.execute <<~SQL
308
365
  CREATE TABLE IF NOT EXISTS cdr_other_patient_types (
@@ -485,6 +542,8 @@ module MalawiHivProgramReports
485
542
  def clear_tables
486
543
  # if locations is empty then we truncating otherwise we clear the locations
487
544
  if locations.empty?
545
+ ActiveRecord::Base.connection.execute('TRUNCATE TABLE cdr_tb_confirmed_and_on_treatment')
546
+ ActiveRecord::Base.connection.execute('TRUNCATE TABLE cdr_tb_screened')
488
547
  ActiveRecord::Base.connection.execute('TRUNCATE TABLE cdr_other_patient_types')
489
548
  ActiveRecord::Base.connection.execute('TRUNCATE TABLE cdr_temp_potential_cohort_members')
490
549
  ActiveRecord::Base.connection.execute('TRUNCATE TABLE cdr_temp_min_drug_orders')
@@ -493,6 +552,8 @@ module MalawiHivProgramReports
493
552
  ActiveRecord::Base.connection.execute('TRUNCATE TABLE cdr_temp_external_clients')
494
553
  ActiveRecord::Base.connection.execute('TRUNCATE TABLE cdr_reason_for_starting_art')
495
554
  else
555
+ ActiveRecord::Base.connection.execute("DELETE FROM cdr_tb_confirmed_and_on_treatment WHERE site_id IN (#{locations.join(',')})")
556
+ ActiveRecord::Base.connection.execute("DELETE FROM cdr_tb_screened WHERE site_id IN (#{locations.join(',')})")
496
557
  ActiveRecord::Base.connection.execute("DELETE FROM cdr_other_patient_types WHERE site_id IN (#{locations.join(',')})")
497
558
  ActiveRecord::Base.connection.execute("DELETE FROM cdr_temp_potential_cohort_members WHERE site_id IN (#{locations.join(',')})")
498
559
  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