malawi_hiv_program_reports 1.0.1 → 1.0.2

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 +2340 -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 +202 -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,219 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ostruct'
4
+
5
+ module MalawiHivProgramReports
6
+ module Moh
7
+ # This class is used to add additional cohort disaggregated data
8
+ # rubocop:disable Metrics/ClassLength
9
+ class CohortStruct
10
+ FIELD_DESCRIPTIONS = {
11
+ # Table contains various fields of the Cohort report and their descriptions.
12
+ # A fields description is simply "Indicator: Human Readable Name",
13
+ # or "Human Readable Name". The former is for fields with an indicator
14
+ # label and the latter is for fields without.
15
+ # Examples:
16
+ # ...,
17
+ # six_a: "KS: Kaposis Sarcoma",
18
+ # regimen_2p: "Regimen 2P",
19
+ # ...
20
+ total_other_patients: 'All others (not circled)',
21
+ patients_with_7_plus_doses_missed_at_their_last_visit: 'Adherence: 4+ Doses',
22
+ unknown_age: 'Unknown age',
23
+ cum_unknown_age: 'Unknown Age (Cumulative)',
24
+ quarterly_unknown_age: 'Unknown Age (Quarterly)',
25
+ died_total: 'Died: Died Total',
26
+ died_within_the_1st_month_of_art_initiation: 'M1: Died within the 1st month after ART initiation',
27
+ died_within_the_2nd_month_of_art_initiation: 'M2: Died within the 2nd month of art initiation',
28
+ died_within_the_3rd_month_of_art_initiation: 'M3: Died within the 3rd month after ART initiation',
29
+ died_after_the_3rd_month_of_art_initiation: 'M4+: Died within the 3rd month after ART initiation',
30
+ no_tb: 'Nev/>2 Years: Never TB or TB over 2 years ago',
31
+ cum_no_tb: 'Nev/>2 Years: Never TB or TB over 2 years ago (Cumulative)',
32
+ quarterly_no_tb: 'Nev/>2 Years: Never TB or TB over 2 years ago (Quarterly)',
33
+ who_stage_two: 'CD4: CD4 below threshold',
34
+ cum_who_stage_two: 'CD4: CD4 below threshold (Cumulative)',
35
+ quarterly_who_stage_two: 'CD4: CD4 below threshold (Quarterly)',
36
+ asymptomatic: 'Asy: Asymptomatic / mild',
37
+ cum_asymptomatic: 'Asy: Asymptomatic / mild (Cumulative)',
38
+ quarterly_asymptomatic: 'Asy: Asymptomatic / mild (Quarterly)',
39
+ pregnant_women: 'Preg: Pregnant women',
40
+ cum_pregnant_women: 'Preg: Pregnant Women (Cumulative)',
41
+ quarterly_pregnant_women: 'Preg: Pregnant Women (Quarterly)',
42
+ defaulted: 'Def: Defaulted (more than 2 months overdue after expected to have run out of ARVs',
43
+ tb_within_the_last_two_years: 'Last 2 Years: TB within the last 2 years',
44
+ cum_tb_within_the_last_two_years: 'Last 2 Years: TB within the last 2 years (Cumulative)',
45
+ quarterly_tb_within_the_last_two_years: 'Last 2 Years: TB within the last 2 years (Quarterly)',
46
+ total_patients_without_side_effects: 'Side Effects (as of the last visit before end of quarter)',
47
+ current_episode_of_tb: 'Curr: Current episode of TB',
48
+ cum_current_episode_of_tb: 'Curr: Current episode of TB (Cumulative)',
49
+ quarterly_current_episode_of_tb: 'Curr: Current episode of TB (Quarterly)',
50
+ re_initiated_on_art: 'Re: Patients re-initiated on ART',
51
+ cum_re_initiated_on_art: 'Re: Patients re-initiated on ART (Cumulative)',
52
+ quarterly_re_initiated_on_art: 'Re: Patients re-initiated on ART (Quarterly)',
53
+ zero_p: 'Regimen 0P',
54
+ zero_a: 'Regimen 0 A',
55
+ two_p: 'Regimen 2P',
56
+ two_a: 'Regimen 2 A',
57
+ four_pp: ' Regimen 4PP',
58
+ four_pa: ' Regimen 4PA',
59
+ four_a: 'Regimen 4 A',
60
+ five_a: 'Regimen 5 A',
61
+ six_a: 'Regimen 6 A',
62
+ seven_a: 'Regimen 7 A',
63
+ eight_a: 'Regimen 8 A',
64
+ nine_pp: 'Regimen 9PP',
65
+ nine_pa: 'Regimen 9PA',
66
+ nine_a: 'Regimen 9 A',
67
+ ten_a: 'Regimen 10 A',
68
+ eleven_pp: 'Regimen 11PP',
69
+ eleven_pa: 'Regimen 11PA',
70
+ eleven_a: 'Regimen 11 A',
71
+ twelve_pp: 'Regimen 12PP',
72
+ twelve_pa: 'Regimen 12PA',
73
+ twelve_a: 'Regimen 12 A',
74
+ thirteen_a: 'Regimen 13 A',
75
+ fourteen_pp: 'Regimen 14PP',
76
+ fourteen_pa: 'Regimen 14PA',
77
+ fourteen_a: 'Regimen 14 A',
78
+ fifteen_pp: 'Regimen 15PP',
79
+ fifteen_pa: 'Regimen 15PA',
80
+ fifteen_a: 'Regimen 15 A',
81
+ sixteen_p: 'Regimen 16P',
82
+ sixteen_a: 'Regimen 16 A',
83
+ seventeen_pa: 'Regimen 17PA',
84
+ seventeen_pp: 'Regimen 17PP',
85
+ seventeen_a: 'Regimen 17 A',
86
+ total_patients_with_side_effects: 'Any side effects',
87
+ total_patients_on_family_planning: 'PIFP: Apprx. % of women who received Depo at ART in the last quarter',
88
+ total_pregnant_women: 'Pregnant/BreastFeeding as of the last visit before end of quarter',
89
+ transfered_out: 'TO: Transferred Out',
90
+ children_12_59_months: 'U5: Children 12 - 59 months',
91
+ cum_children_12_59_months: 'U5: Children 12 - 59 months (Cumulative)',
92
+ quarterly_children_12_59_months: 'U5: Children 12 - 59 months (Quarterly)',
93
+ tb_not_suspected: 'Current TB status any form of TB',
94
+ tb_confirmed_on_tb_treatment: 'TB confirmed, on TB Treatment',
95
+ tb_confirmed_currently_not_yet_on_tb_treatment: 'TB confirmed, not on TB Treatment',
96
+ breastfeeding_mothers: 'BF: Breastfeeding mothers',
97
+ cum_breastfeeding_mothers: 'BF: Breastfeeding mothers (Cumulative)',
98
+ quarterly_breastfeeding_mothers: 'BF: Breastfeeding mothers (Quarterly)',
99
+ patients_with_0_6_doses_missed_at_their_last_visit: 'Adnerence: as of the last visit before end of quarter',
100
+ total_patients_on_arvs_and_ipt: 'IPT: Apprx. % of patients retained in <b>ART</b> who are currently on IPT',
101
+ total_breastfeeding_women: 'Total Breastfeeding Women',
102
+ total_alive_and_on_art: ' Total alive and on ART',
103
+ kaposis_sarcoma: "KS: Kaposi's Sarcoma",
104
+ cum_kaposis_sarcoma: "KS: Kaposi's Sarcoma (Cumulative)",
105
+ quarterly_kaposis_sarcoma: "KS: Kaposi's Sarcoma (Quarterly)",
106
+ unknown_outcome: 'Patient status is unknown',
107
+ unknown_regimen: "Specify above regimens counted as 'Other' Other (paed. / adult)",
108
+ total_patients_with_screened_bp: 'BP screen: Apprx. % of adult ART patients with BP recorded at least once this year',
109
+ cum_total_registered: 'Total Registered (Cummulative)',
110
+ quarterly_total_registered: 'Total Registered (Quarterly)',
111
+ transfer_in: 'TI: Patients transferred in on ART',
112
+ cum_transfer_in: 'TI: Patients transferred in on ART (Cumulative)',
113
+ quarterly_transfer_in: 'TI: Patients transferred in on ART (Quarterly)',
114
+ confirmed_hiv_infection_in_infants_pcr: 'PCR: Infants < 12 months PCR+',
115
+ cum_confirmed_hiv_infection_in_infants_pcr: 'PCR: Infants < 12 months PCR+ (Cumulative)',
116
+ quarterly_confirmed_hiv_infection_in_infants_pcr: 'PCR: Infants < 12 months PCR+ (Quarterly)',
117
+ who_stage_four: '4: WHO stage 4',
118
+ cum_who_stage_four: '4: WHO stage 4 (Cumulative)',
119
+ quarterly_who_stage_four: '4: WHO stage 4 (Quarterly)',
120
+ non_pregnant_females: 'FNP: Non-pregnant females all ages',
121
+ cum_non_pregnant_females: 'FNP: Non-pregnant females all ages (Cumulative)',
122
+ quarterly_non_pregnant_females: 'FNP: Non-pregnant females all ages (Quarterly)',
123
+ unknown_tb_status: 'Unknown (not circled)',
124
+ total_patients_on_arvs_and_cpt: 'CPT: Apprx. % of patients retained in <b>ART</b> who are currently on CPT',
125
+ tb_suspected: 'TB Suspected',
126
+ unknown_side_effects: 'Unkown (not circled)',
127
+ total_registered: 'Total Registered',
128
+ pregnant_females_all_ages: 'Female pregnant_females all ages',
129
+ cum_pregnant_females_all_ages: 'Female pregnant patients all ages (Cumulative)',
130
+ quarterly_pregnant_females_all_ages: 'Cumulative female pregnant patients (Quarterly)',
131
+ patients_with_unknown_adhrence: 'Unknown (not circled)',
132
+ who_stage_three: '3: WHO stage 3',
133
+ cum_who_stage_three: '3: WHO stage 3 (Cumulative)',
134
+ quarterly_who_stage_three: '3: WHO stage 3 (Quarterly)',
135
+ unknown_other_reason_outside_guidelines: 'Unk: Unknown / reason outside guidelines',
136
+ cum_unknown_other_reason_outside_guidelines: 'Unk: Unknown / reason outside guidelines (Cumulative)',
137
+ quarterly_unknown_other_reason_outside_guidelines: 'Unk: Unknown / reason outside guidelines (Quarterly)',
138
+ initiated_on_art_first_time: 'FT: Patients initiated on ART first time',
139
+ cum_initiated_on_art_first_time: 'FT: Patients initiated on ART first time (Cummulative)',
140
+ quarterly_initiated_on_art_first_time: 'FT: Patients initiated on ART first time (Quarterly)',
141
+ presumed_severe_hiv_disease_in_infants: 'PSHD: Pres. Sev. HIV disease age < 12 months',
142
+ cum_presumed_severe_hiv_disease_in_infants: 'PSHD: Pres. Sev. HIV disease age < 12 months (Cumulative)',
143
+ quarterly_presumed_severe_hiv_disease_in_infants: 'PSHD: Pres. Sev. HIV disease age < 12 months (Quarterly)',
144
+ all_males: 'M: Males all ages',
145
+ cum_all_males: 'M: Males all ages (Cumulative)',
146
+ quarterly_all_males: 'M: Males all ages (Quarterly)',
147
+ stopped_art: 'Stop: Stopped taking ARVs (clinician or patient own decision, last known alive)',
148
+ children_below_24_months_at_art_initiation: 'A: Children below 24 months at ART initiation',
149
+ cum_children_below_24_months_at_art_initiation: 'A: Children below 24 months at ART initiation (Cumulative)',
150
+ quarterly_children_below_24_months_at_art_initiation: 'A: Children below 24 m at ART initiation (Quarterly)',
151
+ children_24_months_14_years_at_art_initiation: 'B: Children 24 months - 14 years at ART initiation',
152
+ cum_children_24_months_14_years_at_art_initiation: 'B: Children 24 months - 14 years at ART initiation (Cumulative)',
153
+ quarterly_children_24_months_14_years_at_art_initiation: 'B: Children 24 months - 14 years at ART initiation (Quarterly)',
154
+ adults_at_art_initiation: 'C: Adults 15 years or older at ART initiation',
155
+ cum_adults_at_art_initiation: 'C: Adults 15 years or older at ART initiation (Cumulative)',
156
+ quarterly_adults_at_art_initiation: 'C: Adults 15 years or older at ART initiation (Quarterly)',
157
+ males_initiated_on_art_first_time: 'Newly initiated male patients',
158
+ cum_males_initiated_on_art_first_time: 'Newly initiated male patients (Cummulative)',
159
+ initial_pregnant_females_all_ages: 'Newly initiated pregnant females',
160
+ cum_initial_pregnant_females_all_ages: 'Newly initiated pregnant females (Cummulative)',
161
+ initial_non_pregnant_females_all_ages: 'Newly initiated non-pregnant females',
162
+ cum_initial_non_pregnant_females_all_ages: 'Newly initiated non-pregnant females (Cummulative)',
163
+ unknown_gender: 'All clients registered but has not gender specified',
164
+ cum_unknown_gender: 'All clients registered but has not gender specified (Cummulative)',
165
+ newly_initiated_on_3hp: 'All patients who started 3HP in current reporting period',
166
+ newly_initiated_on_ipt: 'All patients who started IPT in current reporting period'
167
+ }.freeze
168
+
169
+ def initialize
170
+ @values = ActiveSupport::HashWithIndifferentAccess.new
171
+ end
172
+
173
+ def method_missing(name, *args, &)
174
+ name_prefix, name_suffix = split_missing_method_name(name)
175
+
176
+ return super(name, *args, &) unless FIELD_DESCRIPTIONS.include?(name_prefix)
177
+
178
+ field = value(name_prefix)
179
+ field.contents = args[0] if name_suffix == '='
180
+ field.contents
181
+
182
+ field.contents
183
+ end
184
+
185
+ def respond_to_missing?(name)
186
+ field_name, = split_missing_method_name(name)
187
+ FIELD_DESCRIPTIONS.include?(field_name)
188
+ end
189
+
190
+ def values
191
+ @values.values
192
+ end
193
+
194
+ private
195
+
196
+ # Returns a ReportValue object for the given name
197
+ def value(name)
198
+ description = FIELD_DESCRIPTIONS[name]
199
+ iname_parts = description.split(':', 2)
200
+ iname_parts.insert(0, nil) unless iname_parts.size == 2
201
+ short_iname, long_iname = iname_parts
202
+
203
+ @values[name] ||= OpenStruct.new(
204
+ name:,
205
+ indicator_name: long_iname.strip,
206
+ indicator_short_name: short_iname ? short_iname.strip : short_iname,
207
+ description:,
208
+ contents: nil
209
+ )
210
+ end
211
+
212
+ def split_missing_method_name(name)
213
+ match = name.to_s.match(/^([_A-Z0-9]+)(=)?$/i)
214
+ [match[1].to_sym, match[2]]
215
+ end
216
+ end
217
+ # rubocop:enable Metrics/ClassLength
218
+ end
219
+ end
@@ -0,0 +1,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MalawiHivProgramReports
4
+ module Moh
5
+ # This class is used to add additional cohort disaggregated data
6
+ # rubocop:disable Metrics/ClassLength
7
+ class CohortSurvivalAnalysis
8
+ def initialize(name:, type:, start_date:, end_date:, regenerate:, occupation:)
9
+ @name = name
10
+ @type = type
11
+ @start_date = start_date
12
+ @end_date = end_date
13
+ @regenerate = regenerate
14
+ @occupation = occupation
15
+ end
16
+
17
+ def survival_analysis(quarter, age_group)
18
+ art_service = CohortDisaggregated.new(name: 'survival_analysis',
19
+ type: 'survival_analysis', start_date: Date.today,
20
+ end_date: Date.today, rebuild: @regenerate)
21
+
22
+ start_date, end_date = art_service.generate_start_date_and_end_date(quarter)
23
+ CohortBuilder.new.init_temporary_tables(start_date, @end_date, @occupation) if @regenerate
24
+ art_service = CohortDisaggregated.new(name: 'survival_analysis',
25
+ type: 'survival_analysis', start_date: @start_date.to_date,
26
+ end_date: @end_date.to_date, rebuild: @regenerate)
27
+
28
+ qtr = quarter.split[0]
29
+ results = {}
30
+ years = 1
31
+
32
+ years_to_backto = ActiveRecord::Base.connection.select_one <<~SQL
33
+ SELECT
34
+ TIMESTAMPDIFF(YEAR, DATE(MIN(encounter_datetime)), DATE('#{end_date}')) years
35
+ FROM encounter e WHERE program_id = 1 AND voided = 0;
36
+ SQL
37
+
38
+ clinic_start_years = begin
39
+ years_to_backto['years'].to_i
40
+ rescue StandardError
41
+ nil
42
+ end
43
+ clinic_start_years = 10 if clinic_start_years.blank?
44
+
45
+ while years < clinic_start_years
46
+ yr = (quarter.split[1].to_i - years)
47
+ set_qtr = "#{qtr} #{yr}"
48
+ qstart_date, qend_date = art_service.generate_start_date_and_end_date(set_qtr)
49
+ results[set_qtr] = {}
50
+
51
+ case age_group
52
+ when 'General'
53
+ additional_sql = ' GROUP BY e.patient_id'
54
+ when 'Children'
55
+ additional_sql = ' GROUP BY e.patient_id'
56
+ additional_sql += ' HAVING patient_age < 15'
57
+ when 'Women'
58
+ option_Bplus_women_ids = pregnant_and_breastfeeding_women(qstart_date, qend_date)
59
+
60
+ option_Bplus_women_ids = [0] if option_Bplus_women_ids.blank?
61
+
62
+ additional_sql = " AND e.patient_id IN (#{option_Bplus_women_ids.join(', ')})"
63
+ additional_sql += ' GROUP BY e.patient_id'
64
+ additional_sql += ' HAVING patient_age >= 15'
65
+ additional_sql += ' AND gender = "F"'
66
+ end
67
+
68
+ begin
69
+ data = ActiveRecord::Base.connection.select_all <<~SQL
70
+ SELECT
71
+ cum_outcome, timestampdiff(month, DATE('#{qend_date}'), DATE('#{end_date}')) qinterval,
72
+ timestampdiff(year, DATE(e.birthdate), DATE('#{end_date}')) AS patient_age,
73
+ e.gender
74
+ FROM temp_earliest_start_date e
75
+ INNER JOIN temp_patient_outcomes o ON o.patient_id = e.patient_id
76
+ WHERE date_enrolled BETWEEN '#{qstart_date.strftime('%Y-%m-%d')}'
77
+ AND '#{qend_date.strftime('%Y-%m-%d')}'
78
+ #{additional_sql};
79
+ SQL
80
+ rescue StandardError
81
+ return results
82
+ end
83
+
84
+ (data || []).each do |r|
85
+ outcome = r['cum_outcome']
86
+ outcome = (outcome.blank? ? 'Unknown' : outcome)
87
+
88
+ if results[set_qtr][outcome].blank?
89
+ results[set_qtr][outcome] = {}
90
+ results[set_qtr][outcome][r['qinterval']] = 0
91
+ end
92
+ results[set_qtr][outcome][r['qinterval']] += 1
93
+ end
94
+
95
+ years += 1
96
+ end
97
+
98
+ append_last_six_months(quarter, results, end_date) if age_group == 'Women'
99
+
100
+ results
101
+ end
102
+
103
+ def pregnant_and_breastfeeding_women(start_date, end_date)
104
+ patient_ids = []
105
+
106
+ patients = ActiveRecord::Base.connection.select_all <<~SQL
107
+ SELECT
108
+ e.*, patient_reason_for_starting_art_text(e.patient_id) reason
109
+ FROM temp_earliest_start_date e
110
+ WHERE date_enrolled BETWEEN '#{start_date.to_date}' AND '#{end_date.to_date}'
111
+ AND gender IN('F','Female') GROUP BY e.patient_id
112
+ HAVING reason LIKE '%pregnant%' OR reason LIKE '%breast%';
113
+ SQL
114
+
115
+ (patients || []).each do |aRow|
116
+ patient_ids << aRow['patient_id'].to_i
117
+ end
118
+
119
+ concept_ids = []
120
+ ::ConceptName.where(name: 'Breastfeeding').select do |c|
121
+ concept_ids << c.concept_id
122
+ end
123
+ concept_ids << ::ConceptName.find_by_name('Patient pregnant').concept_id
124
+ concept_ids << ::ConceptName.find_by_name('Is patient pregnant?').concept_id
125
+ concept_ids << ::ConceptName.find_by_name('Is patient breast feeding?').concept_id
126
+ yes_concept_id = ::ConceptName.find_by_name('Yes').concept_id
127
+
128
+ patients = ActiveRecord::Base.connection.select_all <<~SQL
129
+ SELECT
130
+ e.*, patient_reason_for_starting_art_text(e.patient_id) reason
131
+ FROM temp_earliest_start_date e
132
+ INNER JOIN obs ON obs.person_id = e.patient_id
133
+ WHERE date_enrolled BETWEEN '#{start_date.to_date}' AND '#{end_date.to_date}'
134
+ AND gender IN('F','Female') AND obs.voided = 0
135
+ AND DATE(obs_datetime) = DATE(earliest_start_date)
136
+ AND obs.concept_id IN(#{concept_ids.join(',')})
137
+ AND value_coded = #{yes_concept_id} GROUP BY e.patient_id
138
+ HAVING reason LIKE '%Lymphocyte count below threshold with who stage%'
139
+ ORDER BY obs_datetime DESC;
140
+ SQL
141
+
142
+ pregnant_and_breastfeeding_clients = []
143
+
144
+ (patients || []).each do |aRow|
145
+ pregnant_and_breastfeeding_clients << aRow['patient_id'].to_i
146
+ end
147
+
148
+ (patient_ids + pregnant_and_breastfeeding_clients).uniq
149
+ end
150
+
151
+ def append_last_six_months(quarter, results, end_date)
152
+ art_service = CohortDisaggregated.new(name: 'survival_analysis',
153
+ type: 'survival_analysis', start_date: @start_date.to_date,
154
+ end_date: @end_date.to_date, rebuild: @regenerate)
155
+
156
+ qstart_date, qend_date = art_service.generate_start_date_and_end_date(quarter)
157
+ qstart_date -= 6.month
158
+ qend_date -= 6.month
159
+ # set_qtr = "Q#{quarter.split(' ')[0][1..1].to_i - 2} #{qend_date.year}"
160
+ set_qtr = (quarter[1..1].to_i - 2)
161
+
162
+ set_qtr = (set_qtr.zero? ? 4 : set_qtr)
163
+ set_qtr = (set_qtr == -1 ? 3 : set_qtr)
164
+ set_qtr = (set_qtr == -2 ? 2 : set_qtr)
165
+ set_qtr = "Q#{set_qtr} #{qend_date.year}"
166
+
167
+ option_Bplus_women_ids = pregnant_and_breastfeeding_women(qstart_date, qend_date)
168
+ option_Bplus_women_ids = [0] if option_Bplus_women_ids.blank?
169
+
170
+ data = ActiveRecord::Base.connection.select_all <<~SQL
171
+ SELECT
172
+ cum_outcome, timestampdiff(month, DATE('#{qend_date}'), DATE('#{end_date}')) qinterval,
173
+ timestampdiff(year, DATE(e.birthdate), DATE('#{end_date}')) AS patient_age,
174
+ e.gender
175
+ FROM temp_earliest_start_date e
176
+ INNER JOIN temp_patient_outcomes o ON o.patient_id = e.patient_id
177
+ WHERE date_enrolled BETWEEN '#{qstart_date.strftime('%Y-%m-%d')}'
178
+ AND '#{qend_date.strftime('%Y-%m-%d')}' AND gender = 'F' AND e.patient_id IN (#{option_Bplus_women_ids.join(', ')});
179
+ SQL
180
+
181
+ (data || []).each do |r|
182
+ outcome = r['cum_outcome']
183
+ outcome = (outcome.blank? ? 'Unknown' : outcome)
184
+
185
+ if results[set_qtr].blank?
186
+ results[set_qtr] = {}
187
+ results[set_qtr][outcome] = {}
188
+ results[set_qtr][outcome][r['qinterval']] = 0
189
+ elsif results[set_qtr][outcome].blank?
190
+ results[set_qtr][outcome] = {}
191
+ results[set_qtr][outcome][r['qinterval']] = 0
192
+ end
193
+
194
+ puts "######### #{r['qinterval']}"
195
+ results[set_qtr][outcome][r['qinterval']] += 1
196
+ end
197
+
198
+ results
199
+ end
200
+ end
201
+ # rubocop:enable Metrics/ClassLength
202
+ end
203
+ end
@@ -0,0 +1,223 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MalawiHivProgramReports
4
+ module Moh
5
+ # This class generates the MOH TPT report
6
+ # rubocop:disable Metrics/ClassLength
7
+ class MohTpt
8
+ include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
9
+ attr_reader :start_date, :end_date, :start_of_month, :end_of_month, :raw_end_date, :raw_start_date
10
+
11
+ def initialize(start_date:, end_date:, **kwargs)
12
+ @raw_end_date = end_date.to_date
13
+ @raw_start_date = start_date.to_date
14
+ @start_date = start_date - 9.months
15
+ @end_date = @start_date + 3.months
16
+ @start_of_month = @start_date.beginning_of_month
17
+ @end_of_month = @end_date.end_of_month
18
+ @occupation = kwargs[:occupation]
19
+ end
20
+
21
+ def data
22
+ report = init_report
23
+ drop_tables
24
+ initiated_on_art
25
+ initiated_on_tpt
26
+ create_indexes
27
+ process_initiated_on_art report, fetch_initiated_on_art
28
+ process_initiated_tpt report, fetch_initiated_on_tpt
29
+ response = []
30
+ report.each do |key, value|
31
+ response << { age_group: key, gender: 'F', **value['F'] }
32
+ response << { age_group: key, gender: 'M', **value['M'] }
33
+ end
34
+ response
35
+ end
36
+
37
+ private
38
+
39
+ GENDERS = %w[F M].freeze
40
+ AGE_GROUPS = ['<1 year', '1-4 years', '5-9 years', '10-14 years', '15-19 years',
41
+ '20-24 years', '25-29 years', '30-34 years', '35-39 years', '40-44 years',
42
+ '45-49 years', '50-54 years', '55-59 years', '60-64 years', '65-69 years',
43
+ '70-74 years', '75-79 years', '80-84 years', '85-89 years',
44
+ '90 plus years'].freeze
45
+
46
+ def init_report
47
+ AGE_GROUPS.each_with_object({}) do |age_group, report|
48
+ report[age_group] = GENDERS.each_with_object({}) do |gender, tpt_report|
49
+ tpt_report[gender] = {
50
+ initiated_art: [], started_tpt: [], not_completed_tpt: [],
51
+ completed_tpt: [], died: [], pregnant: [],
52
+ defaulted: [], stopped: [],
53
+ transfer_out: [], confirmed_tb: [],
54
+ breast_feeding: [], skin_rash: [],
55
+ nausea: [], peripheral_neuropathy: [],
56
+ dizziness: [], yellow_eyes: []
57
+ }
58
+ end
59
+ end
60
+ end
61
+
62
+ def process_initiated_on_art(report, patients)
63
+ patients.each do |patient|
64
+ report[patient['age_group']][patient['gender']][:initiated_art] << patient['patient_id']
65
+ end
66
+ end
67
+
68
+ def process_initiated_tpt(report, patients)
69
+ TptOutcome.new(start_date:, end_date: raw_end_date)
70
+ .moh_report(report, patients, start_date, end_date)
71
+ end
72
+
73
+ def fetch_initiated_on_art
74
+ ActiveRecord::Base.connection.select_all <<~SQL
75
+ SELECT patient_id, age_group, gender FROM temp_initiated_on_art
76
+ WHERE art_start_date >= DATE('#{start_of_month}')
77
+ SQL
78
+ end
79
+
80
+ def fetch_initiated_on_tpt
81
+ result = ActiveRecord::Base.connection.select_all <<~SQL
82
+ SELECT * FROM temp_initiated_on_tpt
83
+ WHERE start_date >= DATE('#{start_of_month}')
84
+ SQL
85
+ # convert to array of hashes
86
+ result.to_a
87
+ end
88
+
89
+ def initiated_on_art
90
+ ActiveRecord::Base.connection.execute <<~SQL
91
+ CREATE TABLE temp_initiated_on_art
92
+ SELECT pp.patient_id, coalesce(o.value_datetime, min(art_order.start_date)) art_start_date, p.gender, disaggregated_age_group(p.birthdate, DATE('#{raw_end_date}')) age_group
93
+ FROM patient_program pp
94
+ INNER JOIN person p ON p.person_id = pp.patient_id AND p.voided = 0
95
+ INNER JOIN patient_state ps ON ps.patient_program_id = pp.patient_program_id AND ps.voided = 0 AND ps.state = 7 -- ON ART
96
+ INNER JOIN orders art_order ON art_order.patient_id = pp.patient_id
97
+ AND art_order.start_date >= DATE('#{start_of_month}')
98
+ AND art_order.start_date < DATE('#{end_of_month}') + INTERVAL 1 DAY
99
+ AND art_order.voided = 0
100
+ AND art_order.order_type_id = 1 -- Drug order
101
+ AND art_order.concept_id IN (#{arv_concepts})
102
+ INNER JOIN drug_order do ON do.order_id = art_order.order_id AND do.quantity > 0
103
+ LEFT JOIN encounter e ON e.patient_id = pp.patient_id
104
+ AND e.encounter_type = 9 -- HIV CLINIC REGISTRATION
105
+ AND e.voided = 0
106
+ AND e.encounter_datetime < DATE('#{end_of_month}') + INTERVAL 1 DAY
107
+ AND e.program_id = 1 -- HIV PROGRAM
108
+ LEFT JOIN obs o ON o.person_id = pp.patient_id
109
+ AND o.concept_id = 2516 -- ART start date
110
+ AND o.encounter_id = e.encounter_id
111
+ AND o.voided = 0
112
+ LEFT JOIN (#{current_occupation_query}) a ON a.person_id = pp.patient_id
113
+ WHERE pp.patient_id NOT IN (
114
+ SELECT o.patient_id
115
+ FROM orders o
116
+ INNER JOIN drug_order do ON do.order_id = o.order_id AND do.quantity > 0
117
+ WHERE o.order_type_id = 1 -- Drug order
118
+ AND o.voided = 0
119
+ AND o.concept_id IN (#{arv_concepts})
120
+ AND o.start_date < DATE('#{start_of_month}')
121
+ GROUP BY o.patient_id
122
+ )
123
+ AND pp.program_id = 1 #{%w[Military Civilian].include?(@occupation) ? 'AND' : ''} #{occupation_filter(occupation: @occupation, field_name: 'value', table_name: 'a', include_clause: false)}
124
+ GROUP BY pp.patient_id
125
+ SQL
126
+ end
127
+
128
+ def initiated_on_tpt
129
+ ActiveRecord::Base.connection.execute <<~SQL
130
+ CREATE TABLE temp_initiated_on_tpt
131
+ SELECT
132
+ pp.patient_id,
133
+ coalesce(tpt_transfer_in_obs.value_datetime, min(tpt_order.start_date)) start_date,
134
+ p.gender, disaggregated_age_group(p.birthdate, DATE('#{raw_end_date}')) age_group,
135
+ patient_outcome(p.person_id, DATE('#{@raw_end_date}')) AS outcome,
136
+ DATE(COALESCE(art_start_date_obs.value_datetime, MIN(art_order.start_date))) AS earliest_start_date,
137
+ GROUP_CONCAT(DISTINCT tpt_order.concept_id SEPARATOR ',') AS drug_concepts,
138
+ CASE
139
+ WHEN count(DISTINCT tpt_order.concept_id) > 1 THEN '3HP'
140
+ WHEN tpt_order.concept_id = 10565 THEN '3HP'
141
+ ELSE '6H'
142
+ END AS tpt_type
143
+ FROM patient_program pp
144
+ INNER JOIN person p ON p.person_id = pp.patient_id AND p.voided = 0
145
+ INNER JOIN patient_state ps ON ps.patient_program_id = pp.patient_program_id AND ps.voided = 0 AND ps.state = 7 -- ON ART
146
+ INNER JOIN orders tpt_order ON tpt_order.patient_id = pp.patient_id
147
+ AND tpt_order.start_date >= DATE('#{start_of_month}')
148
+ AND tpt_order.start_date < DATE('#{end_of_month}') + INTERVAL 1 DAY
149
+ AND tpt_order.voided = 0
150
+ AND tpt_order.order_type_id = 1 -- Drug order
151
+ AND tpt_order.concept_id IN (#{tpt_concepts})
152
+ INNER JOIN drug_order do ON do.order_id = tpt_order.order_id AND do.quantity > 0
153
+ LEFT JOIN encounter AS clinic_registration_encounter
154
+ ON clinic_registration_encounter.encounter_type = (
155
+ SELECT encounter_type_id FROM encounter_type WHERE name = 'HIV CLINIC REGISTRATION' LIMIT 1
156
+ )
157
+ AND clinic_registration_encounter.patient_id = pp.patient_id
158
+ AND clinic_registration_encounter.program_id = pp.program_id
159
+ AND clinic_registration_encounter.encounter_datetime < DATE('#{@end_date}') + INTERVAL 1 DAY
160
+ AND clinic_registration_encounter.voided = 0
161
+ INNER JOIN orders AS art_order
162
+ ON art_order.patient_id = pp.patient_id
163
+ /* AND art_order.encounter_id = prescription_encounter.encounter_id */
164
+ AND art_order.concept_id IN (SELECT concept_id FROM concept_set WHERE concept_set = 1085)
165
+ AND art_order.start_date < DATE('#{@end_of_month}') + INTERVAL 1 DAY
166
+ AND art_order.order_type_id IN (SELECT order_type_id FROM order_type WHERE name = 'Drug order')
167
+ AND art_order.start_date >= DATE('1901-01-01')
168
+ AND art_order.voided = 0
169
+ LEFT JOIN obs AS art_start_date_obs
170
+ ON art_start_date_obs.concept_id = 2516
171
+ AND art_start_date_obs.person_id = pp.patient_id
172
+ AND art_start_date_obs.voided = 0
173
+ AND art_start_date_obs.obs_datetime < (DATE('#{@end_of_month}') + INTERVAL 1 DAY)
174
+ AND art_start_date_obs.encounter_id = clinic_registration_encounter.encounter_id
175
+ LEFT JOIN obs tpt_transfer_in_obs ON tpt_transfer_in_obs.person_id = pp.patient_id
176
+ AND tpt_transfer_in_obs.concept_id = #{::ConceptName.find_by_name('TPT Drugs Received').concept_id}
177
+ AND tpt_transfer_in_obs.voided = 0
178
+ AND tpt_transfer_in_obs.value_drug IN (#{tpt_concepts})
179
+ AND tpt_transfer_in_obs.obs_datetime < DATE('#{end_of_month}') + INTERVAL 1 DAY
180
+ LEFT JOIN (#{current_occupation_query}) a ON a.person_id = pp.patient_id
181
+ WHERE pp.patient_id NOT IN (
182
+ SELECT o.patient_id
183
+ FROM orders o
184
+ INNER JOIN drug_order do ON do.order_id = o.order_id AND do.quantity > 0
185
+ WHERE o.order_type_id = 1 -- Drug order
186
+ AND o.voided = 0
187
+ AND o.concept_id IN (#{tpt_concepts})
188
+ AND o.start_date < DATE('#{start_of_month}')
189
+ GROUP BY o.patient_id
190
+ )
191
+ AND pp.program_id = 1 #{%w[Military Civilian].include?(@occupation) ? 'AND' : ''} #{occupation_filter(occupation: @occupation, field_name: 'value', table_name: 'a', include_clause: false)}
192
+ GROUP BY pp.patient_id
193
+ SQL
194
+ end
195
+
196
+ def drop_tables
197
+ execute_query 'DROP TABLE IF EXISTS temp_initiated_on_art'
198
+ execute_query 'DROP TABLE IF EXISTS temp_initiated_on_tpt'
199
+ end
200
+
201
+ def create_indexes
202
+ execute_query 'CREATE INDEX idx_temp_initiated_on_art ON temp_initiated_on_art(patient_id)'
203
+ execute_query 'CREATE INDEX idx_temp_initiated_on_tpt ON temp_initiated_on_tpt(patient_id)'
204
+ end
205
+
206
+ def execute_query(query)
207
+ ActiveRecord::Base.connection.execute(query)
208
+ end
209
+
210
+ def arv_concepts
211
+ @arv_concepts ||= ::ConceptSet.where(concept_set: ::ConceptName.where(name: 'Antiretroviral drugs')
212
+ .select(:concept_id))
213
+ .collect(&:concept_id).join(',')
214
+ end
215
+
216
+ def tpt_concepts
217
+ @tpt_concepts ||= ::ConceptName.where(name: ['Rifapentine', 'Isoniazid', 'Isoniazid/Rifapentine'])
218
+ .collect(&:concept_id).join(',')
219
+ end
220
+ end
221
+ # rubocop:enable Metrics/ClassLength
222
+ end
223
+ end