malawi_hiv_program_reports 1.0.1 → 1.0.2

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.
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,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