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.
- 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 +2340 -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 +202 -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
|