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