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.
- checksums.yaml +4 -4
- data/app/services/malawi_hiv_program_reports/README.md +16 -0
- data/app/services/malawi_hiv_program_reports/adapters/moh/custom.rb +199 -0
- data/app/services/malawi_hiv_program_reports/archiving_candidates.rb +130 -0
- data/app/services/malawi_hiv_program_reports/arv_refill_periods.rb +311 -0
- data/app/services/malawi_hiv_program_reports/clinic/README.md +5 -0
- data/app/services/malawi_hiv_program_reports/clinic/appointments_report.rb +317 -0
- data/app/services/malawi_hiv_program_reports/clinic/discrepancy_report.rb +42 -0
- data/app/services/malawi_hiv_program_reports/clinic/docs/hypertension_report.md +31 -0
- data/app/services/malawi_hiv_program_reports/clinic/drug_dispensations.rb +48 -0
- data/app/services/malawi_hiv_program_reports/clinic/external_consultation_clients.rb +69 -0
- data/app/services/malawi_hiv_program_reports/clinic/hypertension_report.rb +223 -0
- data/app/services/malawi_hiv_program_reports/clinic/ipt_coverage.rb +112 -0
- data/app/services/malawi_hiv_program_reports/clinic/ipt_report.rb +69 -0
- data/app/services/malawi_hiv_program_reports/clinic/lims_results.rb +55 -0
- data/app/services/malawi_hiv_program_reports/clinic/outcome_list.rb +127 -0
- data/app/services/malawi_hiv_program_reports/clinic/patients_alive_and_on_treatment.rb +57 -0
- data/app/services/malawi_hiv_program_reports/clinic/patients_due_for_viral_load.rb +39 -0
- data/app/services/malawi_hiv_program_reports/clinic/patients_on_antiretrovirals.rb +44 -0
- data/app/services/malawi_hiv_program_reports/clinic/patients_on_dtg.rb +36 -0
- data/app/services/malawi_hiv_program_reports/clinic/patients_on_treatment.rb +42 -0
- data/app/services/malawi_hiv_program_reports/clinic/patients_with_outdated_demographics.rb +173 -0
- data/app/services/malawi_hiv_program_reports/clinic/pregnant_patients.rb +91 -0
- data/app/services/malawi_hiv_program_reports/clinic/regimen_dispensation_data.rb +282 -0
- data/app/services/malawi_hiv_program_reports/clinic/regimen_switch.rb +456 -0
- data/app/services/malawi_hiv_program_reports/clinic/regimens_and_formulations.rb +182 -0
- data/app/services/malawi_hiv_program_reports/clinic/regimens_by_weight_and_gender.rb +108 -0
- data/app/services/malawi_hiv_program_reports/clinic/retention.rb +246 -0
- data/app/services/malawi_hiv_program_reports/clinic/stock_card_report.rb +65 -0
- data/app/services/malawi_hiv_program_reports/clinic/tpt_outcome.rb +494 -0
- data/app/services/malawi_hiv_program_reports/clinic/tx_rtt.rb +169 -0
- data/app/services/malawi_hiv_program_reports/clinic/viral_load.rb +292 -0
- data/app/services/malawi_hiv_program_reports/clinic/viral_load_disaggregated.rb +97 -0
- data/app/services/malawi_hiv_program_reports/clinic/viral_load_results.rb +175 -0
- data/app/services/malawi_hiv_program_reports/clinic/visits_report.rb +113 -0
- data/app/services/malawi_hiv_program_reports/clinic/vl_collection.rb +48 -0
- data/app/services/malawi_hiv_program_reports/cohort/outcomes.rb +338 -0
- data/app/services/malawi_hiv_program_reports/cohort/regimens.rb +69 -0
- data/app/services/malawi_hiv_program_reports/cohort/side_effects.rb +141 -0
- data/app/services/malawi_hiv_program_reports/cohort/tpt.rb +172 -0
- data/app/services/malawi_hiv_program_reports/moh/cohort.rb +278 -0
- data/app/services/malawi_hiv_program_reports/moh/cohort_builder.rb +2337 -0
- data/app/services/malawi_hiv_program_reports/moh/cohort_disaggregated.rb +608 -0
- data/app/services/malawi_hiv_program_reports/moh/cohort_disaggregated_additions.rb +208 -0
- data/app/services/malawi_hiv_program_reports/moh/cohort_disaggregated_builder.rb +526 -0
- data/app/services/malawi_hiv_program_reports/moh/cohort_struct.rb +219 -0
- data/app/services/malawi_hiv_program_reports/moh/cohort_survival_analysis.rb +203 -0
- data/app/services/malawi_hiv_program_reports/moh/moh_tpt.rb +223 -0
- data/app/services/malawi_hiv_program_reports/moh/tpt_newly_initiated.rb +235 -0
- data/app/services/malawi_hiv_program_reports/pepfar/defaulter_list.rb +25 -0
- data/app/services/malawi_hiv_program_reports/pepfar/maternal_status.rb +29 -0
- data/app/services/malawi_hiv_program_reports/pepfar/patient_start_vl.rb +45 -0
- data/app/services/malawi_hiv_program_reports/pepfar/regimen_switch.rb +479 -0
- data/app/services/malawi_hiv_program_reports/pepfar/sc_arvdisp.rb +174 -0
- data/app/services/malawi_hiv_program_reports/pepfar/sc_curr.rb +98 -0
- data/app/services/malawi_hiv_program_reports/pepfar/tb_prev.rb +163 -0
- data/app/services/malawi_hiv_program_reports/pepfar/tb_prev2.rb +222 -0
- data/app/services/malawi_hiv_program_reports/pepfar/tb_prev3.rb +421 -0
- data/app/services/malawi_hiv_program_reports/pepfar/tpt_status.rb +181 -0
- data/app/services/malawi_hiv_program_reports/pepfar/tx_ml.rb +181 -0
- data/app/services/malawi_hiv_program_reports/pepfar/tx_new.rb +205 -0
- data/app/services/malawi_hiv_program_reports/pepfar/tx_rtt.rb +288 -0
- data/app/services/malawi_hiv_program_reports/pepfar/tx_tb.rb +283 -0
- data/app/services/malawi_hiv_program_reports/pepfar/utils.rb +141 -0
- data/app/services/malawi_hiv_program_reports/pepfar/viral_load_coverage.rb +414 -0
- data/app/services/malawi_hiv_program_reports/pepfar/viral_load_coverage2.rb +433 -0
- data/app/services/malawi_hiv_program_reports/report_map.rb +56 -0
- data/app/services/malawi_hiv_program_reports/utils/README.md +8 -0
- data/app/services/malawi_hiv_program_reports/utils/common_sql_query_utils.rb +60 -0
- data/app/services/malawi_hiv_program_reports/utils/concurrency_utils.rb +53 -0
- data/app/services/malawi_hiv_program_reports/utils/docs/common_sql_query_utils.md +53 -0
- data/app/services/malawi_hiv_program_reports/utils/model_utils.rb +66 -0
- data/app/services/malawi_hiv_program_reports/utils/parameter_utils.rb +32 -0
- data/app/services/malawi_hiv_program_reports/utils/time_utils.rb +52 -0
- data/lib/malawi_hiv_program_reports/version.rb +1 -1
- 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
|