malawi_hiv_program_reports 1.0.1 → 1.0.2
Sign up to get free protection for your applications and to get access to all the features.
- 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,108 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MalawiHivProgramReports
|
4
|
+
module Clinic
|
5
|
+
class RegimensByWeightAndGender
|
6
|
+
include MalawiHivProgramReports::Utils::ConcurrencyUtils
|
7
|
+
|
8
|
+
attr_reader :start_date, :end_date
|
9
|
+
|
10
|
+
def initialize(start_date:, end_date:, **kwargs)
|
11
|
+
@start_date = start_date
|
12
|
+
@end_date = end_date
|
13
|
+
@rebuild_outcomes = true
|
14
|
+
@occupation = kwargs[:occupation]
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_report
|
18
|
+
regimen_counts
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
WEIGHT_BANDS = [
|
24
|
+
[3, 3.9],
|
25
|
+
[4, 4.9],
|
26
|
+
[6, 9.9],
|
27
|
+
[10, 13.9],
|
28
|
+
[14, 19.9],
|
29
|
+
[20, 24.9],
|
30
|
+
[25, 29.9],
|
31
|
+
[30, 34.9],
|
32
|
+
[35, 39.9],
|
33
|
+
[40, 49.9],
|
34
|
+
[50, Float::INFINITY],
|
35
|
+
[nil, nil] # To capture all those missing weight
|
36
|
+
].freeze
|
37
|
+
|
38
|
+
def regimen_counts
|
39
|
+
with_lock(Cohort::LOCK_FILE) do
|
40
|
+
PatientsAliveAndOnTreatment.new(start_date:, end_date:, occupation: @occupation)
|
41
|
+
.refresh_outcomes_table
|
42
|
+
|
43
|
+
WEIGHT_BANDS.map do |start_weight, end_weight|
|
44
|
+
{
|
45
|
+
weight: weight_band_to_string(start_weight, end_weight),
|
46
|
+
males: regimen_counts_by_weight_and_gender(start_weight, end_weight, 'M'),
|
47
|
+
females: regimen_counts_by_weight_and_gender(start_weight, end_weight, 'F'),
|
48
|
+
unknown_gender: regimen_counts_by_weight_and_gender(start_weight, end_weight, nil)
|
49
|
+
}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def weight_band_to_string(start_weight, end_weight)
|
55
|
+
if start_weight.nil? && end_weight.nil?
|
56
|
+
'Unknown'
|
57
|
+
elsif end_weight == Float::INFINITY
|
58
|
+
"#{start_weight} Kg +"
|
59
|
+
else
|
60
|
+
"#{start_weight} - #{end_weight} Kg"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# TODO: Refactor the queries in this module... Possibly
|
65
|
+
# prefer joins over the subqueries (ie if performance becomes an
|
66
|
+
# issue - it probably will eventually).
|
67
|
+
|
68
|
+
def regimen_counts_by_weight_and_gender(start_weight, end_weight, gender)
|
69
|
+
date = ActiveRecord::Base.connection.quote(end_date)
|
70
|
+
|
71
|
+
query = TempPatientOutcome.joins('INNER JOIN temp_earliest_start_date USING (patient_id)')
|
72
|
+
.select("patient_current_regimen(patient_id, #{date}) as regimen, count(*) AS count")
|
73
|
+
.where(patient_id: patients_in_weight_band(start_weight, end_weight))
|
74
|
+
.where(cum_outcome: 'On Antiretrovirals')
|
75
|
+
.group(:regimen)
|
76
|
+
|
77
|
+
query = gender ? query.where('gender LIKE ?', "#{gender}%") : query.where('gender IS NULL')
|
78
|
+
|
79
|
+
query.collect { |obs| { obs.regimen => obs.count } }
|
80
|
+
end
|
81
|
+
|
82
|
+
def patients_in_weight_band(start_weight, end_weight)
|
83
|
+
if start_weight.nil? && end_weight.nil?
|
84
|
+
# If no weight is provided then this must be all patients without a weight observation
|
85
|
+
return ::PatientProgram.select(:patient_id)
|
86
|
+
.where(program_id: ::ArtService::Constants::PROGRAM_ID)
|
87
|
+
.where.not(patient_id: patients_with_known_weight.select(:person_id))
|
88
|
+
end
|
89
|
+
|
90
|
+
max_weights = patients_with_known_weight.select('MAX(obs_datetime) AS obs_datetime, person_id').to_sql
|
91
|
+
|
92
|
+
patients_with_known_weight
|
93
|
+
.joins("INNER JOIN (#{max_weights}) AS max_weights
|
94
|
+
ON max_weights.person_id = obs.person_id AND max_weights.obs_datetime = obs.obs_datetime")
|
95
|
+
.where(value_numeric: (start_weight..end_weight))
|
96
|
+
.select(:person_id)
|
97
|
+
end
|
98
|
+
|
99
|
+
def patients_with_known_weight
|
100
|
+
::Observation.joins('INNER JOIN temp_patient_outcomes AS outcomes ON outcomes.patient_id = obs.person_id')
|
101
|
+
.where(concept_id: ::ConceptName.where(name: 'Weight (kg)').select(:concept_id),
|
102
|
+
outcomes: { cum_outcome: 'On antiretrovirals' })
|
103
|
+
.where('DATE(obs.obs_datetime) <= ?', end_date)
|
104
|
+
.group(:person_id)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,246 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Retrieve patients who are completing their first 1st, 3rd, and 6th month on ART
|
4
|
+
# in the reporting period.
|
5
|
+
module MalawiHivProgramReports
|
6
|
+
module Clinic
|
7
|
+
class Retention
|
8
|
+
attr_reader :start_date, :end_date
|
9
|
+
|
10
|
+
include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
|
11
|
+
|
12
|
+
DAYS_IN_MONTH = 28
|
13
|
+
MONTHS = [1, 3, 6].freeze
|
14
|
+
|
15
|
+
def initialize(start_date:, end_date:, **kwargs)
|
16
|
+
@start_date = start_date.to_s
|
17
|
+
@end_date = end_date.to_s
|
18
|
+
@use_filing_number = ::GlobalProperty.find_by(property: 'use.filing.numbers')
|
19
|
+
&.property_value
|
20
|
+
&.casecmp?('true')
|
21
|
+
@occupation = kwargs[:occupation]
|
22
|
+
end
|
23
|
+
|
24
|
+
def find_report
|
25
|
+
matched_patients = MONTHS.each_with_object({}) do |month, hash|
|
26
|
+
hash[month] = { retained: [], all: [] }
|
27
|
+
end
|
28
|
+
|
29
|
+
find_patients_retention_period(retained_patients(as_of: Date.parse(start_date) - MONTHS.max.months)) do |period, patient|
|
30
|
+
matched_patients[period][:retained] << {
|
31
|
+
patient_id: patient.patient_id,
|
32
|
+
arv_number: patient.arv_number,
|
33
|
+
start_date: patient.start_date,
|
34
|
+
gender: begin
|
35
|
+
patient.gender.upcase.first
|
36
|
+
rescue StandardError
|
37
|
+
nil
|
38
|
+
end,
|
39
|
+
age_group: patient.age_group,
|
40
|
+
end_date: patient.start_date + period.months
|
41
|
+
}
|
42
|
+
end
|
43
|
+
|
44
|
+
find_patients_retention_period(all_patients(as_of: Date.parse(start_date) - MONTHS.max.months)) do |period, patient|
|
45
|
+
matched_patients[period][:all] << {
|
46
|
+
patient_id: patient.patient_id,
|
47
|
+
arv_number: patient.arv_number,
|
48
|
+
gender: begin
|
49
|
+
patient.gender.upcase.first
|
50
|
+
rescue StandardError
|
51
|
+
nil
|
52
|
+
end,
|
53
|
+
age_group: patient.age_group,
|
54
|
+
start_date: patient.start_date
|
55
|
+
}
|
56
|
+
end
|
57
|
+
|
58
|
+
matched_patients
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_patients_retention_period(patients)
|
62
|
+
patients.each do |patient|
|
63
|
+
retention_period = MONTHS.find do |period|
|
64
|
+
(start_date..end_date).cover?((patient.start_date + period.months).to_s)
|
65
|
+
end
|
66
|
+
|
67
|
+
next unless retention_period
|
68
|
+
|
69
|
+
yield retention_period, patient
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Pull all patients who started medication before the current reporting period but after
|
74
|
+
# the given `as_of` date and have any dispensation that ends in the current reporting
|
75
|
+
# period... That's a mouthful woah!!!
|
76
|
+
def retained_patients(as_of:)
|
77
|
+
start_date = ActiveRecord::Base.connection.quote(self.start_date)
|
78
|
+
end_date = ActiveRecord::Base.connection.quote(self.end_date)
|
79
|
+
as_of = ActiveRecord::Base.connection.quote(as_of)
|
80
|
+
::Order.find_by_sql(
|
81
|
+
<<~SQL
|
82
|
+
SELECT
|
83
|
+
initial_orders.patient_id AS patient_id,
|
84
|
+
DATE(initial_orders.start_date) AS start_date,
|
85
|
+
last_orders.auto_expire_date AS auto_expire_date,
|
86
|
+
patient_identifier.identifier AS arv_number,
|
87
|
+
disaggregated_age_group(p.birthdate, DATE('#{@end_date}')) age_group,
|
88
|
+
p.gender gender
|
89
|
+
FROM orders initial_orders
|
90
|
+
INNER JOIN encounter initial_encounter ON initial_encounter.encounter_id = initial_orders.encounter_id AND initial_encounter.voided = 0 AND initial_encounter.program_id = 1
|
91
|
+
INNER JOIN person p ON p.person_id = initial_orders.patient_id AND p.voided = 0
|
92
|
+
LEFT JOIN patient_identifier ON patient_identifier.patient_id = initial_orders.patient_id AND patient_identifier.identifier_type = #{patient_identifier_type_id}
|
93
|
+
INNER JOIN (
|
94
|
+
SELECT last_order.patient_id, MAX(last_order.auto_expire_date) AS auto_expire_date
|
95
|
+
FROM orders last_order
|
96
|
+
INNER JOIN encounter last_encounter ON last_encounter.encounter_id = last_order.encounter_id AND last_encounter.voided = 0 AND last_encounter.program_id = 1
|
97
|
+
WHERE last_order.auto_expire_date BETWEEN #{start_date} AND #{end_date}
|
98
|
+
AND last_order.order_type_id = #{drug_order_type_id}
|
99
|
+
AND last_order.voided = 0
|
100
|
+
GROUP BY last_order.patient_id
|
101
|
+
) last_orders ON initial_orders.patient_id = last_orders.patient_id
|
102
|
+
LEFT JOIN (#{current_occupation_query}) a ON a.person_id = initial_orders.patient_id
|
103
|
+
WHERE initial_orders.start_date BETWEEN #{as_of} AND #{start_date}
|
104
|
+
AND initial_orders.order_type_id = #{drug_order_type_id} #{%w[Military Civilian].include?(@occupation) ? 'AND' : ''} #{occupation_filter(occupation: @occupation, field_name: 'value', table_name: 'a', include_clause: false)}
|
105
|
+
AND initial_orders.auto_expire_date IS NOT NULL
|
106
|
+
AND initial_orders.patient_id NOT IN (
|
107
|
+
SELECT o.patient_id
|
108
|
+
FROM orders o
|
109
|
+
INNER JOIN encounter e ON e.encounter_id = o.encounter_id AND e.voided = 0 AND e.program_id = 1
|
110
|
+
WHERE o.order_type_id = #{drug_order_type_id} AND o.start_date < #{as_of} AND o.auto_expire_date IS NOT NULL AND o.voided = 0
|
111
|
+
)
|
112
|
+
GROUP BY initial_orders.patient_id
|
113
|
+
SQL
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
def all_patients(as_of:)
|
118
|
+
start_date = ActiveRecord::Base.connection.quote(self.start_date)
|
119
|
+
as_of = ActiveRecord::Base.connection.quote(as_of)
|
120
|
+
|
121
|
+
::Order.find_by_sql(
|
122
|
+
<<~SQL
|
123
|
+
SELECT initial_order.patient_id AS patient_id,
|
124
|
+
DATE(initial_order.start_date) AS start_date,
|
125
|
+
patient_identifier.identifier AS arv_number,
|
126
|
+
disaggregated_age_group(p.birthdate, DATE('#{@end_date}')) age_group,
|
127
|
+
p.gender gender
|
128
|
+
FROM orders initial_order
|
129
|
+
INNER JOIN encounter initial_encounter ON initial_encounter.encounter_id = initial_order.encounter_id AND initial_encounter.program_id = 1
|
130
|
+
INNER JOIN person p ON p.person_id = initial_encounter.patient_id
|
131
|
+
LEFT JOIN patient_identifier ON patient_identifier.patient_id = initial_order.patient_id AND patient_identifier.identifier_type = #{patient_identifier_type_id}
|
132
|
+
LEFT JOIN (#{current_occupation_query}) a ON a.person_id = initial_order.patient_id
|
133
|
+
WHERE initial_order.start_date BETWEEN #{as_of} AND #{start_date}
|
134
|
+
AND initial_order.voided = 0
|
135
|
+
AND initial_order.auto_expire_date IS NOT NULL
|
136
|
+
AND initial_order.order_type_id = #{drug_order_type_id}
|
137
|
+
AND p.voided = 0 #{%w[Military Civilian].include?(@occupation) ? 'AND' : ''} #{occupation_filter(occupation: @occupation, field_name: 'value', table_name: 'a', include_clause: false)}
|
138
|
+
AND initial_order.patient_id NOT IN (
|
139
|
+
SELECT orders.patient_id
|
140
|
+
FROM orders
|
141
|
+
INNER JOIN encounter ON encounter.encounter_id = orders.encounter_id AND encounter.program_id = 1 AND encounter.voided = 0
|
142
|
+
WHERE start_date < #{as_of} AND order_type_id = #{drug_order_type_id} AND orders.voided = 0
|
143
|
+
)
|
144
|
+
GROUP BY initial_order.patient_id
|
145
|
+
SQL
|
146
|
+
)
|
147
|
+
end
|
148
|
+
|
149
|
+
# def retained_patients(as_of:)
|
150
|
+
# start_date = ActiveRecord::Base.connection.quote(self.start_date)
|
151
|
+
# end_date = ActiveRecord::Base.connection.quote(self.end_date)
|
152
|
+
# as_of = ActiveRecord::Base.connection.quote(as_of)
|
153
|
+
|
154
|
+
# ::Order.find_by_sql(
|
155
|
+
# <<~SQL
|
156
|
+
# SELECT initial_order.patient_id AS patient_id,
|
157
|
+
# initial_order.start_date AS start_date,
|
158
|
+
# last_order.auto_expire_date AS auto_expire_date,
|
159
|
+
# patient_identifier.identifier AS arv_number,
|
160
|
+
# disaggregated_age_group(p.birthdate, DATE('#{@end_date}')) age_group,
|
161
|
+
# p.gender gender
|
162
|
+
# FROM orders initial_order
|
163
|
+
# INNER JOIN encounter initial_encounter ON initial_encounter.encounter_id = initial_order.encounter_id AND initial_encounter.program_id = 1
|
164
|
+
# INNER JOIN orders last_order ON last_order.patient_id = initial_order.patient_id
|
165
|
+
# INNER JOIN encounter last_encounter ON last_encounter.encounter_id = last_order.encounter_id
|
166
|
+
# INNER JOIN person p ON p.person_id = initial_encounter.patient_id
|
167
|
+
# LEFT JOIN patient_identifier ON patient_identifier.patient_id = initial_order.patient_id
|
168
|
+
# WHERE initial_order.start_date BETWEEN #{as_of} AND #{start_date}
|
169
|
+
# AND initial_order.voided = 0
|
170
|
+
# AND initial_order.auto_expire_date IS NOT NULL
|
171
|
+
# AND initial_order.order_type_id = #{drug_order_type_id}
|
172
|
+
# AND last_order.auto_expire_date BETWEEN #{start_date} AND #{end_date}
|
173
|
+
# AND last_order.order_type_id = #{drug_order_type_id}
|
174
|
+
# AND last_order.voided = 0
|
175
|
+
# AND p.voided = 0
|
176
|
+
# AND initial_order.start_date = (
|
177
|
+
# SELECT MIN(start_date) FROM orders
|
178
|
+
# WHERE patient_id = initial_order.patient_id
|
179
|
+
# AND start_date BETWEEN #{as_of} AND #{start_date}
|
180
|
+
# AND order_type_id = #{drug_order_type_id}
|
181
|
+
# AND voided = 0
|
182
|
+
# )
|
183
|
+
# AND initial_order.patient_id NOT IN (
|
184
|
+
# SELECT orders.patient_id
|
185
|
+
# FROM orders
|
186
|
+
# INNER JOIN encounter ON encounter.encounter_id = orders.encounter_id AND encounter.program_id = 1
|
187
|
+
# WHERE start_date < #{as_of} AND order_type_id = #{drug_order_type_id} AND orders.voided = 0
|
188
|
+
# )
|
189
|
+
# GROUP BY initial_order.patient_id
|
190
|
+
# SQL
|
191
|
+
# )
|
192
|
+
# end
|
193
|
+
|
194
|
+
# def all_patients(as_of:)
|
195
|
+
# start_date = ActiveRecord::Base.connection.quote(self.start_date)
|
196
|
+
# as_of = ActiveRecord::Base.connection.quote(as_of)
|
197
|
+
|
198
|
+
# ::Order.find_by_sql(
|
199
|
+
# <<~SQL
|
200
|
+
# SELECT initial_order.patient_id AS patient_id,
|
201
|
+
# initial_order.start_date AS start_date,
|
202
|
+
# patient_identifier.identifier AS arv_number,
|
203
|
+
# disaggregated_age_group(p.birthdate, DATE('#{@end_date}')) age_group,
|
204
|
+
# p.gender gender
|
205
|
+
# FROM orders initial_order
|
206
|
+
# INNER JOIN encounter initial_encounter ON initial_encounter.encounter_id = initial_order.encounter_id AND initial_encounter.program_id = 1
|
207
|
+
# INNER JOIN person p ON p.person_id = initial_encounter.patient_id
|
208
|
+
# LEFT JOIN patient_identifier ON patient_identifier.patient_id = initial_order.patient_id AND patient_identifier.identifier_type = #{patient_identifier_type_id}
|
209
|
+
# WHERE initial_order.start_date BETWEEN #{as_of} AND #{start_date}
|
210
|
+
# AND initial_order.voided = 0
|
211
|
+
# AND initial_order.auto_expire_date IS NOT NULL
|
212
|
+
# AND initial_order.order_type_id = #{drug_order_type_id}
|
213
|
+
# AND p.voided = 0
|
214
|
+
# AND initial_order.start_date = (
|
215
|
+
# SELECT MIN(start_date) FROM orders
|
216
|
+
# WHERE patient_id = initial_order.patient_id
|
217
|
+
# AND start_date BETWEEN #{as_of} AND #{start_date}
|
218
|
+
# AND order_type_id = #{drug_order_type_id}
|
219
|
+
# AND voided = 0
|
220
|
+
# )
|
221
|
+
# AND initial_order.patient_id NOT IN (
|
222
|
+
# SELECT orders.patient_id
|
223
|
+
# FROM orders
|
224
|
+
# INNER JOIN encounter ON encounter.encounter_id = orders.encounter_id AND encounter.program_id = 1
|
225
|
+
# WHERE start_date < #{as_of} AND order_type_id = #{drug_order_type_id} AND orders.voided = 0
|
226
|
+
# )
|
227
|
+
# GROUP BY initial_order.patient_id
|
228
|
+
# SQL
|
229
|
+
# )
|
230
|
+
# end
|
231
|
+
|
232
|
+
def drug_order_type_id
|
233
|
+
@drug_order_type_id ||= ::OrderType.find_by_name('Drug order').order_type_id
|
234
|
+
end
|
235
|
+
|
236
|
+
def patient_identifier_type_id
|
237
|
+
return @patient_identifier_type_id if @patient_identifier_type_id
|
238
|
+
|
239
|
+
identifier_type_name = @use_filing_number ? 'Filing Number' : 'ARV Number'
|
240
|
+
identifier_type = ::PatientIdentifierType.find_by_name!(identifier_type_name)
|
241
|
+
|
242
|
+
@patient_identifier_type_id ||= ActiveRecord::Base.connection.quote(identifier_type.id)
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MalawiHivProgramReports
|
4
|
+
module Clinic
|
5
|
+
# Generates a stock card report for a clinic
|
6
|
+
class StockCardReport
|
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
|
+
stock_card_report
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def stock_card_report
|
20
|
+
ActiveRecord::Base.connection.select_all <<~SQL
|
21
|
+
SELECT
|
22
|
+
pbi.drug_id AS drug_id,
|
23
|
+
COALESCE(d.name, 'Unkown') AS drug_name,
|
24
|
+
COALESCE(psb_opening.close_balance, 0)/pbi.pack_size AS opening_balance,
|
25
|
+
COALESCE(psb_closing.close_balance, 0)/pbi.pack_size AS closing_balance,
|
26
|
+
SUM(ABS(po.quantity))/pbi.pack_size AS dispensed_quantity,
|
27
|
+
pbi.pack_size
|
28
|
+
FROM pharmacy_batch_items AS pbi
|
29
|
+
INNER JOIN drug AS d ON d.drug_id = pbi.drug_id
|
30
|
+
LEFT JOIN pharmacy_obs AS po ON po.batch_item_id = pbi.id
|
31
|
+
AND po.voided = 0
|
32
|
+
AND po.pharmacy_encounter_type = 3 -- Pharmacy dispensing
|
33
|
+
AND po.transaction_date BETWEEN #{@start_date} AND #{@end_date}
|
34
|
+
AND po.dispensation_obs_id IS NOT NULL
|
35
|
+
LEFT JOIN (
|
36
|
+
SELECT
|
37
|
+
drug_id,
|
38
|
+
MAX(transaction_date) AS transaction_date,
|
39
|
+
pack_size
|
40
|
+
FROM pharmacy_stock_balances
|
41
|
+
WHERE transaction_date <= #{@end_date}
|
42
|
+
GROUP BY drug_id, pack_size
|
43
|
+
) AS psb_max ON pbi.drug_id = psb_max.drug_id AND pbi.pack_size = psb_max.pack_size
|
44
|
+
LEFT JOIN (
|
45
|
+
SELECT
|
46
|
+
drug_id,
|
47
|
+
MAX(transaction_date) AS transaction_date,
|
48
|
+
pack_size
|
49
|
+
FROM pharmacy_stock_balances
|
50
|
+
WHERE transaction_date < #{@start_date} -- Opening balance can be anything less than end_date
|
51
|
+
GROUP BY drug_id, pack_size
|
52
|
+
) AS psb_min ON pbi.drug_id = psb_min.drug_id AND pbi.pack_size = psb_min.pack_size
|
53
|
+
LEFT JOIN pharmacy_stock_balances AS psb_opening ON
|
54
|
+
pbi.drug_id = psb_opening.drug_id AND pbi.pack_size = psb_opening.pack_size
|
55
|
+
AND psb_opening.transaction_date = psb_min.transaction_date
|
56
|
+
LEFT JOIN pharmacy_stock_balances AS psb_closing ON
|
57
|
+
pbi.drug_id = psb_closing.drug_id AND pbi.pack_size = psb_closing.pack_size
|
58
|
+
AND psb_closing.transaction_date = psb_max.transaction_date
|
59
|
+
WHERE pbi.voided = 0
|
60
|
+
GROUP BY pbi.drug_id, pbi.pack_size
|
61
|
+
SQL
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|