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,317 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Produces an Appointment Report
4
+
5
+ # rubocop:disable Metrics/ClassLength
6
+ module MalawiHivProgramReports
7
+ module Clinic
8
+ class AppointmentsReport
9
+ include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
10
+
11
+ ENCOUNTER_NAMES = [
12
+ 'VITALS', 'HIV STAGING',
13
+ 'APPOINTMENT', 'HIV CLINIC REGISTRATION',
14
+ 'ART_FOLLOWUP', 'TREATMENT', 'UPDATE OUTCOME',
15
+ 'HIV RECEPTION', 'LAB', 'HIV CLINIC CONSULTATION',
16
+ 'DISPENSING', 'LAB ORDERS', 'ART ADHERENCE',
17
+ 'GIVE LAB RESULTS', 'CERVICAL CANCER SCREENING',
18
+ 'HYPERTENSION MANAGEMENT', 'FAST TRACK ASSESMENT'
19
+ ].freeze
20
+
21
+ HIV_ENCOUNTERS = ::EncounterType.where('name IN(?)', ENCOUNTER_NAMES).map(&:id)
22
+
23
+ def initialize(start_date:, end_date:, **kwargs)
24
+ @start_date = start_date
25
+ @end_date = end_date
26
+ @occupation = kwargs[:occupation]
27
+ end
28
+
29
+ # rubocop:disable Metrics/MethodLength
30
+ # rubocop:disable Metrics/AbcSize
31
+ def missed_appointments
32
+ appointments = ::Observation.joins(:encounter)
33
+ .joins("LEFT JOIN (#{current_occupation_query} )AS a ON a.person_id = obs.person_id")
34
+ .merge(appointment_encounters)
35
+ .where.not(person_id: referral_patients.select(:person_id))
36
+ .where(concept: ::ConceptName.where(name: 'Appointment date').select(:concept_id))
37
+ .where('value_datetime BETWEEN ? AND ? AND encounter.program_id = ?',
38
+ @start_date.strftime('%Y-%m-%d 00:00:00'),
39
+ @end_date.strftime('%Y-%m-%d 23:59:59'), 1)
40
+ .where(occupation_filter(occupation: @occupation, field_name: 'value',
41
+ table_name: 'a', include_clause: false).to_s)
42
+ .group(:person_id)
43
+
44
+ appointments.each_with_object([]) do |appointment, patients|
45
+ patient = missed_appointment?(appointment)
46
+
47
+ patients << patient unless patient.blank?
48
+ end
49
+ end
50
+ # rubocop:enable Metrics/AbcSize
51
+ # rubocop:enable Metrics/MethodLength
52
+
53
+ # rubocop:disable Metrics/MethodLength
54
+ # rubocop:disable Metrics/AbcSize
55
+ # rubocop:disable Metrics/PerceivedComplexity
56
+ # rubocop:disable Metrics/CyclomaticComplexity
57
+ def patient_visit_types
58
+ yes_concept = ::ConceptName.find_by_name('YES').concept_id
59
+ hiv_reception_breakdown = {}
60
+
61
+ (patient_visits || []).each do |v|
62
+ visit_date = v['obs_datetime'].to_date
63
+ visit_type = v['name']
64
+ ans_given = v['value_coded'].to_i == yes_concept
65
+ patient_id = v['patient_id'].to_i
66
+ patient_present = (visit_type.match(/patient/i) && ans_given ? true : false)
67
+ guardian_present = (visit_type.match(/person/i) && ans_given ? true : false)
68
+
69
+ if hiv_reception_breakdown[visit_date].blank?
70
+ hiv_reception_breakdown[visit_date] = {}
71
+ hiv_reception_breakdown[visit_date][patient_id] = {
72
+ patient_present: 0, guardian_present: 0
73
+ }
74
+ elsif hiv_reception_breakdown[visit_date][patient_id].blank?
75
+ hiv_reception_breakdown[visit_date][patient_id] = {
76
+ patient_present: false, guardian_present: false
77
+ }
78
+ end
79
+
80
+ if /patient/i.match?(visit_type)
81
+ hiv_reception_breakdown[visit_date][patient_id][:patient_present] = patient_present
82
+ end
83
+ if /person/i.match?(visit_type)
84
+ hiv_reception_breakdown[visit_date][patient_id][:guardian_present] = guardian_present
85
+ end
86
+ end
87
+
88
+ hiv_reception_breakdown
89
+ end
90
+ # rubocop:enable Metrics/CyclomaticComplexity
91
+ # rubocop:enable Metrics/PerceivedComplexity
92
+ # rubocop:enable Metrics/AbcSize
93
+ # rubocop:enable Metrics/MethodLength
94
+
95
+ # rubocop:disable Metrics/MethodLength
96
+ # rubocop:disable Metrics/AbcSize
97
+ # rubocop:disable Metrics/PerceivedComplexity
98
+ # rubocop:disable Metrics/CyclomaticComplexity
99
+ def patient_visit_list
100
+ yes_concept = ::ConceptName.find_by_name('YES').concept_id
101
+ hiv_reception_breakdown = {}
102
+
103
+ (patient_visits || []).each do |v|
104
+ # visit_date = v['obs_datetime'].to_date
105
+ visit_type = v['name']
106
+ ans_given = v['value_coded'].to_i == yes_concept
107
+ patient_id = v['patient_id'].to_i
108
+ patient_present = (visit_type.match(/patient/i) && ans_given ? true : false)
109
+ guardian_present = (visit_type.match(/person/i) && ans_given ? true : false)
110
+
111
+ if hiv_reception_breakdown[patient_id].blank?
112
+ demographics = client_data(patient_id)
113
+ hiv_reception_breakdown[patient_id] = {
114
+ patient_present: false, guardian_present: false,
115
+ given_name: demographics['given_name'],
116
+ family_name: demographics['family_name'],
117
+ gender: demographics['gender'],
118
+ birthdate: demographics['birthdate'],
119
+ arv_number: demographics['arv_number']
120
+ }
121
+ end
122
+
123
+ hiv_reception_breakdown[patient_id][:patient_present] = patient_present if /patient/i.match?(visit_type)
124
+ hiv_reception_breakdown[patient_id][:guardian_present] = guardian_present if /person/i.match?(visit_type)
125
+ end
126
+
127
+ hiv_reception_breakdown
128
+ end
129
+ # rubocop:enable Metrics/CyclomaticComplexity
130
+ # rubocop:enable Metrics/PerceivedComplexity
131
+ # rubocop:enable Metrics/AbcSize
132
+ # rubocop:enable Metrics/MethodLength
133
+
134
+ private
135
+
136
+ def client_data(patient_id)
137
+ ActiveRecord::Base.connection.select_one <<~SQL
138
+ SELECT
139
+ n.given_name, n.family_name, p.birthdate, p.gender,
140
+ i.identifier arv_number, a.value cell_number,
141
+ s.state_province district, s.county_district ta,
142
+ s.city_village village
143
+ FROM person p
144
+ LEFT JOIN person_name n ON n.person_id = p.person_id
145
+ LEFT JOIN patient_identifier i ON i.patient_id = p.person_id
146
+ AND i.voided = 0 AND i.identifier_type = 4
147
+ LEFT JOIN person_attribute a ON a.person_id = p.person_id
148
+ AND a.voided = 0 AND a.person_attribute_type_id = 12
149
+ LEFT JOIN person_address s ON s.person_id = p.person_id
150
+ AND s.voided = 0 WHERE p.person_id = #{patient_id}
151
+ GROUP BY p.person_id, DATE(p.date_created)
152
+ ORDER BY p.person_id, p.date_created;
153
+ SQL
154
+ end
155
+
156
+ def patient_visits
157
+ encounter_type = ::EncounterType.find_by_name('HIV RECEPTION')
158
+
159
+ ::Observation.joins("INNER JOIN encounter e ON e.encounter_id = obs.encounter_id
160
+ INNER JOIN concept_name c ON c.concept_id = obs.concept_id")
161
+ .where('encounter_type = ? AND (obs_datetime BETWEEN ? AND ?)',
162
+ encounter_type.id, @start_date.strftime('%Y-%m-%d 00:00:00'),
163
+ @end_date.strftime('%Y-%m-%d 23:59:59'))
164
+ .select("e.patient_id, obs.obs_datetime, c.name,
165
+ c.concept_id, obs.value_coded").group("DATE(obs.obs_datetime),
166
+ e.patient_id, c.concept_id").order('obs_datetime ASC')
167
+ end
168
+
169
+ def missed_appointment?(obs)
170
+ client_came?(obs.person_id, obs.value_datetime, obs.encounter.encounter_datetime.to_date + 1.day)
171
+ end
172
+
173
+ def client_came?(person_id, value_datetime, day_after_visit_date)
174
+ # we need to check if the client had a dispensing encounter
175
+ # check if the client was given arv drugs via the orders table and drug_order table
176
+ # on the drug_order table check if the quantity column is greater than 0
177
+ # if the quantity is greater than 0 then the client was given drugs
178
+ # arv drugs are easily found by ::Drug.arv_drugs
179
+ value_datetime = value_datetime.to_date + 14.day
180
+ encounters = ::Encounter.joins(:orders)
181
+ .joins("INNER JOIN drug_order d ON d.order_id = orders.order_id AND d.quantity > 0
182
+ AND d.drug_inventory_id IN(#{arv_drugs})")
183
+ .where(patient_id: person_id, encounter_type: treatment_encounter)
184
+ .where('encounter_datetime BETWEEN ? AND ?',
185
+ day_after_visit_date.strftime('%Y-%m-%d 00:00:00'),
186
+ value_datetime.strftime('%Y-%m-%d 23:59:59'))
187
+ .where(orders: { voided: 0 })
188
+ .where(voided: 0)
189
+
190
+ client_info(person_id, value_datetime - 14.day) if encounters.blank? && client_alive?(person_id, value_datetime)
191
+ end
192
+
193
+ def client_alive?(person_id, value_datetime)
194
+ # check if the client is alive and doesn't have an adverse outcome
195
+ result = ActiveRecord::Base.connection.select_one <<~SQL
196
+ SELECT patient_outcome(#{person_id}, '#{value_datetime.to_date}') outcome;
197
+ SQL
198
+
199
+ result['outcome'].match(/Patient died|Patient transferred out|Treatment stopped/i).blank?
200
+ end
201
+
202
+ def arv_drugs
203
+ @arv_drugs ||= ::Drug.arv_drugs.pluck(:drug_id).join(',')
204
+ end
205
+
206
+ def treatment_encounter
207
+ @treatment_encounter ||= ::EncounterType.find_by_name('TREATMENT').id
208
+ end
209
+
210
+ # rubocop:disable Metrics/MethodLength
211
+ # rubocop:disable Metrics/AbcSize
212
+ def client_info(patient_id, appointment_date)
213
+ person = ActiveRecord::Base.connection.select_one <<~SQL
214
+ SELECT
215
+ n.given_name, n.family_name, p.birthdate, p.gender,
216
+ i.identifier arv_number, a.value cell_number,
217
+ s.state_province district, s.county_district ta,
218
+ s.city_village village
219
+ FROM person p
220
+ LEFT JOIN person_name n ON n.person_id = p.person_id
221
+ LEFT JOIN patient_identifier i ON i.patient_id = p.person_id
222
+ AND i.voided = 0 AND i.identifier_type = 4
223
+ LEFT JOIN person_attribute a ON a.person_id = p.person_id
224
+ AND a.voided = 0 AND a.person_attribute_type_id = 12
225
+ LEFT JOIN person_address s ON s.person_id = p.person_id
226
+ AND s.voided = 0 WHERE p.person_id = #{patient_id}
227
+ GROUP BY p.person_id, DATE(p.date_created)
228
+ ORDER BY p.person_id, p.date_created;
229
+ SQL
230
+
231
+ current_outcome = get_current_outcome(patient_id)
232
+ if current_outcome.match(/died/i) || current_outcome.match(/transfer/i) || current_outcome.match(/stop/i)
233
+ return nil
234
+ end
235
+
236
+ {
237
+ given_name: person['given_name'],
238
+ family_name: person['family_name'],
239
+ birthdate: person['birthdate'],
240
+ gender: person['gender'],
241
+ cell_number: person['cell_number'],
242
+ district: person['district'],
243
+ ta: person['ta'],
244
+ village: person['village'],
245
+ arv_number: (person['arv_number'].blank? ? 'N/A' : person['arv_number']),
246
+ appointment_date: appointment_date.to_date,
247
+ days_missed: days_missed(appointment_date.to_date),
248
+ current_outcome:,
249
+ person_id: patient_id
250
+ }
251
+ end
252
+ # rubocop:enable Metrics/MethodLength
253
+ # rubocop:enable Metrics/AbcSize
254
+
255
+ def days_missed(set_date)
256
+ missed_days = ActiveRecord::Base.connection.select_one <<~SQL
257
+ SELECT TIMESTAMPDIFF(day, DATE('#{set_date}'), DATE('#{@end_date}')) days;
258
+ SQL
259
+
260
+ missed_days['days'].to_i
261
+ end
262
+
263
+ # rubocop:disable Metrics/MethodLength
264
+ def eventually_came_on(patient_id, date)
265
+ data = ActiveRecord::Base.connection.select_one <<~SQL
266
+ SELECT MIN(encounter_datetime) visit_date FROM encounter
267
+ WHERE patient_id = #{patient_id}
268
+ AND encounter_type IN(#{HIV_ENCOUNTERS.join(',')})
269
+ AND encounter_datetime > '#{date.to_date.strftime('%Y-%m-%d 23:59:59')}';
270
+ SQL
271
+
272
+ begin
273
+ data['visit_date'].to_date
274
+ rescue StandardError
275
+ nil
276
+ end
277
+ end
278
+ # rubocop:enable Metrics/MethodLength
279
+
280
+ def concept(name)
281
+ ::ConceptName.find_by_name name
282
+ end
283
+
284
+ def encounter_type(name)
285
+ ::EncounterType.find_by_name name
286
+ end
287
+
288
+ def get_current_outcome(patient_id)
289
+ current_outcome_info = ActiveRecord::Base.connection.select_one <<~SQL
290
+ SELECT patient_outcome(#{patient_id}, DATE('#{@end_date}')) as outcome;
291
+ SQL
292
+
293
+ current_outcome_info['outcome']
294
+ end
295
+
296
+ def referral_patients
297
+ ::Observation.where(concept: ::ConceptName.where(name: 'Type of patient').select(:concept_id),
298
+ value_coded: ::ConceptName.where("name = 'External consultation' OR name = 'Drug refill'")
299
+ .select(:concept_id),
300
+ person_id: registration_encounters.select(:patient_id))
301
+ .where('obs_datetime < DATE(?) + INTERVAL 1 DAY', @end_date)
302
+ .distinct(:person_id)
303
+ end
304
+
305
+ def appointment_encounters
306
+ ::Encounter.where(program: ::Program.where(name: 'HIV PROGRAM'),
307
+ type: ::EncounterType.where(name: 'Appointment'))
308
+ end
309
+
310
+ def registration_encounters
311
+ ::Encounter.where(program: ::Program.where(name: 'HIV PROGRAM'),
312
+ type: ::EncounterType.where(name: 'Registration'))
313
+ end
314
+ end
315
+ end
316
+ end
317
+ # rubocop:enable Metrics/ClassLength
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MalawiHivProgramReports
4
+ module Clinic
5
+ # Generates a discrepancy report for a clinic
6
+ class DiscrepancyReport
7
+ def initialize(start_date:, end_date:, **_kwargs)
8
+ @start_date = ActiveRecord::Base.connection.quote(start_date)
9
+ @end_date = ActiveRecord::Base.connection.quote(end_date)
10
+ end
11
+
12
+ def find_report
13
+ # TODO: Implement this
14
+ discrepancy_report
15
+ end
16
+
17
+ private
18
+
19
+ def discrepancy_report
20
+ ActiveRecord::Base.connection.select_all <<~SQL
21
+ SELECT
22
+ pbi.drug_id,
23
+ d.name,
24
+ d.short_name,
25
+ psv.verification_date,
26
+ psv.reason as verification_reason,
27
+ po_expected.quantity expected_quantity,
28
+ po.quantity difference,
29
+ po_expected.quantity + po.quantity as current_quantity,
30
+ po.transaction_reason as variance_reason
31
+ FROM pharmacy_stock_verifications psv
32
+ INNER JOIN pharmacy_obs po ON po.stock_verification_id = psv.id AND po.voided = 0 AND po.obs_group_id IS NULL
33
+ INNER JOIN pharmacy_batch_items pbi ON pbi.id = po.batch_item_id AND pbi.voided = 0
34
+ INNER JOIN drug_cms d ON d.drug_inventory_id = pbi.drug_id AND d.voided = 0
35
+ LEFT JOIN pharmacy_obs po_expected ON po_expected.obs_group_id = po.pharmacy_module_id AND po_expected.voided = 0
36
+ WHERE psv.verification_date BETWEEN #{@start_date} AND #{@end_date}
37
+ GROUP BY psv.id
38
+ SQL
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,31 @@
1
+ # Code Documentation
2
+
3
+ ## Summary
4
+ The code snippet is a part of the HypertensionReport class in the Clinic module. It initializes a report structure with nested hashes to store hypertension data based on age groups and gender. It also defines methods to process data and populate the report structure.
5
+
6
+ ## Example Usage
7
+ ```ruby
8
+ report = Hypertension::Report.new(start_date: '2021-01-01', end_date: '2021-12-31')
9
+ report.find_report
10
+ ```
11
+ The full namespace
12
+ ```ruby
13
+ Clinic::Hypertension::Report.new(start_date: '2021-01-01', end_date: '2021-12-31')
14
+ ```
15
+
16
+ ## Code Analysis
17
+ ### Inputs
18
+ ```start_date``` (String): The start date of the report period.
19
+ ```end_date``` (String): The end date of the report period.
20
+
21
+ ## Flow
22
+ The ```init_report``` method is called to initialize the report structure with nested hashes for each age group and gender combination.
23
+ The ```initialize_gender_metrics``` method is called to initialize the metrics hash for each gender.
24
+ The ```process_data``` method is called to process the data and populate the report structure.
25
+ The ```data``` method is called to fetch the necessary data from the database using a SQL query.
26
+ The fetched data is iterated over, and the relevant information is extracted and stored in the report structure.
27
+ The report structure is returned as the result of the ```find_report``` method.
28
+
29
+ ## Outputs
30
+ The output of the ```find_report``` method is the report structure, which is a nested hash containing hypertension data categorized by age groups and gender.
31
+
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ ##
4
+ # MER drug dispensation report
5
+ #
6
+ # Captures total number of packs of each drug dispensed in a given time period.
7
+ module MalawiHivProgramReports
8
+ module Clinic
9
+ class DrugDispensations
10
+ attr_reader :start_date, :end_date
11
+
12
+ def initialize(start_date:, end_date:, **_kwargs)
13
+ @start_date = ActiveRecord::Base.connection.quote(start_date)
14
+ @end_date = ActiveRecord::Base.connection.quote(end_date)
15
+ end
16
+
17
+ def find_report
18
+ ActiveRecord::Base.connection.select_all <<~SQL
19
+ SELECT drug.name AS drug_name,
20
+ dispensation.value_numeric AS pack_size,
21
+ COUNT(*) AS packs_dispensed
22
+ FROM obs AS dispensation
23
+ INNER JOIN encounter
24
+ ON encounter.encounter_id = dispensation.encounter_id
25
+ AND encounter.program_id IN (SELECT program_id FROM program WHERE name = 'HIV PROGRAM')
26
+ AND encounter.encounter_type IN (SELECT encounter_type_id FROM encounter_type WHERE name = 'Dispensing')
27
+ AND encounter.voided = 0
28
+ INNER JOIN orders
29
+ ON orders.order_id = dispensation.order_id
30
+ AND orders.start_date BETWEEN #{start_date} AND #{end_date}
31
+ AND orders.order_type_id IN (SELECT order_type_id FROM order_type WHERE name = 'Drug order')
32
+ AND orders.voided = 0
33
+ INNER JOIN drug_order
34
+ ON drug_order.order_id = orders.order_id
35
+ AND drug_order.drug_inventory_id IN (SELECT drug_id FROM arv_drug)
36
+ AND drug_order.quantity > 0
37
+ INNER JOIN drug
38
+ ON drug.drug_id = drug_order.drug_inventory_id
39
+ AND drug.retired = 0
40
+ WHERE dispensation.voided = 0
41
+ AND dispensation.value_numeric > 0
42
+ AND dispensation.concept_id IN (SELECT concept_id FROM concept_name WHERE name = 'Amount dispensed' AND voided = 0)
43
+ GROUP BY dispensation.value_numeric
44
+ SQL
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal:true
2
+
3
+ # Fetches all clients that are not new patients at between the specified period
4
+
5
+ # Exteranal Consultation Clients
6
+ module MalawiHivProgramReports
7
+ module Clinic
8
+ class ExternalConsultationClients
9
+ include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
10
+
11
+ def initialize(start_date:, end_date:, **kwargs)
12
+ @start_date = start_date.to_date
13
+ @end_date = end_date.to_date
14
+ @occupation = kwargs[:occupation]
15
+ end
16
+
17
+ def list
18
+ other_clients
19
+ end
20
+
21
+ private
22
+
23
+ def other_clients
24
+ ext_consultation_concept_id = ::ConceptName.find_by_name('External consultation').concept_id
25
+ drug_refill_concept_id = ::ConceptName.find_by_name('Drug refill').concept_id
26
+ # internal_client_concept_id = ::ConceptName.find_by_name('New patient').concept_id
27
+ type_of_client_concept_id = ::ConceptName.find_by_name('Type of patient').concept_id
28
+ npid_identifier_type_id = ::PatientIdentifierType.find_by_name('National ID').id
29
+
30
+ possible_clients = ActiveRecord::Base.connection.select_all <<~SQL
31
+ SELECT
32
+ p.person_id patient_id, npid.identifier npid, main.value_coded,
33
+ p.birthdate, p.gender, main.obs_datetime, n.family_name, n.given_name, main.value_coded
34
+ FROM obs main
35
+ INNER JOIN person p ON p.person_id = main.person_id
36
+ LEFT JOIN patient_identifier npid ON npid.patient_id = p.person_id
37
+ LEFT JOIN (#{current_occupation_query}) a ON a.person_id = p.person_id
38
+ AND npid.identifier_type = #{npid_identifier_type_id} AND npid.voided = 0
39
+ LEFT JOIN person_name n ON n.person_id = p.person_id AND n.voided = 0
40
+ INNER JOIN (
41
+ SELECT person_id, MAX(obs_datetime) as obs_datetime, concept_id FROM obs
42
+ WHERE concept_id = #{type_of_client_concept_id}
43
+ AND voided = 0
44
+ AND obs.obs_datetime BETWEEN "#{@start_date.strftime('%Y-%m-%d 00:00:00')}"
45
+ AND "#{@end_date.strftime('%Y-%m-%d 23:59:59')}"
46
+ GROUP BY person_id
47
+ ) sub_group ON main.person_id = sub_group.person_id
48
+ AND main.obs_datetime = sub_group.obs_datetime
49
+ AND main.concept_id = sub_group.concept_id
50
+ WHERE main.value_coded IN (#{ext_consultation_concept_id}, #{drug_refill_concept_id}) #{%w[Military Civilian].include?(@occupation) ? 'AND' : ''} #{occupation_filter(occupation: @occupation, field_name: 'value', table_name: 'a', include_clause: false)}
51
+ ORDER BY n.date_created DESC
52
+ SQL
53
+
54
+ (possible_clients || []).map do |client|
55
+ {
56
+ patient_id: client['patient_id'].to_i,
57
+ patient_type: client['value_coded'] == ext_consultation_concept_id ? 'External consultation' : 'Drug refill',
58
+ npid: client['npid'],
59
+ birthdate: client['birthdate'].to_date,
60
+ gender: client['gender'],
61
+ given_name: client['given_name'],
62
+ family_name: client['family_name'],
63
+ date_set: client['obs_datetime'].to_date
64
+ }
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end