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,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Retrieve all patients who are taking ARVs in a given time period.
|
4
|
+
module MalawiHivProgramReports
|
5
|
+
module Clinic
|
6
|
+
class PatientsOnAntiretrovirals
|
7
|
+
attr_reader :start_date, :end_date
|
8
|
+
|
9
|
+
def initialize(start_date:, end_date:, **_kwargs)
|
10
|
+
@start_date = start_date
|
11
|
+
@end_date = end_date
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.within(start_date, end_date)
|
15
|
+
PatientsOnAntiretrovirals.new(start_date:, end_date:)
|
16
|
+
.patients
|
17
|
+
end
|
18
|
+
|
19
|
+
def find_report
|
20
|
+
patients
|
21
|
+
end
|
22
|
+
|
23
|
+
def patients
|
24
|
+
::DrugOrder.select('orders.patient_id AS patient_id')
|
25
|
+
.joins(:order)
|
26
|
+
.merge(art_orders)
|
27
|
+
.where(drug_inventory_id: ArtService::RegimenEngine.arv_drugs,
|
28
|
+
quantity: 1..Float::INFINITY)
|
29
|
+
.where('start_date BETWEEN :start_date AND :end_date
|
30
|
+
OR auto_expire_date BETWEEN :start_date AND :end_date
|
31
|
+
OR (start_date <= :start_date AND auto_expire_date >= :end_date)',
|
32
|
+
start_date:, end_date:)
|
33
|
+
.group('orders.patient_id')
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def art_orders
|
39
|
+
::Order.joins(:encounter)
|
40
|
+
.where('encounter.program_id = ?', ::ArtService::Constants::PROGRAM_ID)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MalawiHivProgramReports
|
4
|
+
module Clinic
|
5
|
+
class PatientsOnDtg
|
6
|
+
attr_reader :start_date, :end_date
|
7
|
+
|
8
|
+
HIV_PROGRAM_ID = 1
|
9
|
+
ARV_NUMBER_TYPE_ID = 4
|
10
|
+
|
11
|
+
def initialize(start_date:, end_date:, **_)
|
12
|
+
@start_date = start_date
|
13
|
+
@end_date = end_date
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_report
|
17
|
+
::DrugOrder.joins(:order)
|
18
|
+
.joins('INNER JOIN encounter USING (encounter_id)')
|
19
|
+
.joins('LEFT JOIN patient_identifier ON patient_identifier.patient_id = orders.patient_id')
|
20
|
+
.where(drug: dtg_drugs,
|
21
|
+
encounter: { program_id: HIV_PROGRAM_ID },
|
22
|
+
patient_identifier: { identifier_type: ARV_NUMBER_TYPE_ID })
|
23
|
+
.where('start_date BETWEEN ? AND ?', start_date, end_date)
|
24
|
+
.group('orders.patient_id')
|
25
|
+
.select('identifier')
|
26
|
+
.map(&:identifier)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def dtg_drugs
|
32
|
+
::Drug.where(concept_id: ::ConceptName.find_by_name('Dolutegravir').concept_id)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Reports on all patients that were on treatment in given time period
|
4
|
+
# regardless of whether they have a terminal state or not within the
|
5
|
+
# period.
|
6
|
+
module MalawiHivProgramReports
|
7
|
+
module Clinic
|
8
|
+
class PatientsOnTreatment
|
9
|
+
attr_reader :start_date, :end_date
|
10
|
+
|
11
|
+
def initialize(start_date:, end_date:)
|
12
|
+
@start_date = start_date
|
13
|
+
@end_date = end_date
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns patients that were on treatment within the given time period.
|
17
|
+
def self.within(start_date, end_date)
|
18
|
+
sql_conditions =
|
19
|
+
<<~SQL
|
20
|
+
((start_date >= :start_date AND start_date <= :end_date)
|
21
|
+
OR (end_date >= :start_date AND end_date <= :end_date)
|
22
|
+
OR (start_date < :start_date AND end_date IS NULL))
|
23
|
+
AND state = :state
|
24
|
+
SQL
|
25
|
+
|
26
|
+
on_arvs = ::PatientState.where(sql_conditions, start_date:,
|
27
|
+
end_date:,
|
28
|
+
state: ::ArtService::Constants::States::ON_ANTIRETROVIRALS)
|
29
|
+
|
30
|
+
::PatientProgram.select('DISTINCT patient_program.patient_id')
|
31
|
+
.joins(:patient_states)
|
32
|
+
.merge(on_arvs)
|
33
|
+
.where(program_id: ::ArtService::Constants::PROGRAM_ID)
|
34
|
+
end
|
35
|
+
|
36
|
+
# We an interface to satisfy, let's be good citizens
|
37
|
+
def find_report
|
38
|
+
within(start_date, end_date).map(&:patient_id)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Retrieves all current(neither dead nor transferred out) patients missing
|
4
|
+
# any demographics or haven't had their demographics updated since the
|
5
|
+
# given date
|
6
|
+
module MalawiHivProgramReports
|
7
|
+
module Clinic
|
8
|
+
class PatientsWithOutdatedDemographics
|
9
|
+
attr_reader :date, :variant
|
10
|
+
|
11
|
+
def initialize(start_date:, variant: 'poc', **_kwargs)
|
12
|
+
@date = start_date
|
13
|
+
@variant = parse_variant(variant)
|
14
|
+
end
|
15
|
+
|
16
|
+
def find_report
|
17
|
+
patients = if variant == 'poc'
|
18
|
+
poc_patients_missing_demographics
|
19
|
+
else
|
20
|
+
emastercard_patients_missing_demographics
|
21
|
+
end
|
22
|
+
|
23
|
+
patients.collect do |patient|
|
24
|
+
{
|
25
|
+
patient_id: patient.patient_id,
|
26
|
+
arv_number: patient.arv_number,
|
27
|
+
given_name: patient.given_name,
|
28
|
+
family_name: patient.family_name,
|
29
|
+
birthdate: patient[:birthdate],
|
30
|
+
gender: patient[:gender],
|
31
|
+
current_village: patient.current_village,
|
32
|
+
current_traditional_authority: patient.current_traditional_authority,
|
33
|
+
current_district: patient.current_district,
|
34
|
+
home_village: patient.home_village,
|
35
|
+
home_traditional_authority: patient.home_traditional_authority,
|
36
|
+
home_district: patient.home_district,
|
37
|
+
landmark: patient.landmark,
|
38
|
+
address_last_updated_date: patient.address_last_updated_date
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Find all patients missing any of the following variables:
|
44
|
+
# - birthdate
|
45
|
+
# - gender
|
46
|
+
# - name
|
47
|
+
# - address
|
48
|
+
def poc_patients_missing_demographics
|
49
|
+
quoted_date = ActiveRecord::Base.connection.quote(date)
|
50
|
+
|
51
|
+
::Patient.find_by_sql(
|
52
|
+
<<~SQL
|
53
|
+
SELECT patient.patient_id AS patient_id,
|
54
|
+
patient_identifier.identifier AS arv_number,
|
55
|
+
person_name.given_name AS given_name,
|
56
|
+
person_name.family_name AS family_name,
|
57
|
+
person.birthdate AS birthdate,
|
58
|
+
person.gender AS gender,
|
59
|
+
person_address.city_village AS current_village,
|
60
|
+
person_address.township_division AS current_traditional_authority,
|
61
|
+
person_address.state_province AS current_district,
|
62
|
+
person_address.neighborhood_cell AS home_village,
|
63
|
+
person_address.county_district AS home_traditional_authority,
|
64
|
+
person_address.address2 AS home_district,
|
65
|
+
landmark.value AS landmark,
|
66
|
+
person_address.date_created AS address_last_updated_date
|
67
|
+
FROM patient
|
68
|
+
INNER JOIN person ON person.person_id = patient.patient_id AND person.voided = 0
|
69
|
+
INNER JOIN patient_program ON patient_program.patient_id = patient.patient_id AND patient_program.program_id = 1
|
70
|
+
INNER JOIN patient_state ON patient_state.patient_program_id = patient_program.patient_program_id
|
71
|
+
LEFT JOIN patient_identifier ON patient_identifier.patient_id = patient.patient_id
|
72
|
+
AND patient_identifier.voided = 0 AND patient_identifier.identifier_type = #{arv_number_id}
|
73
|
+
LEFT JOIN person_name ON person_name.person_id = patient.patient_id AND person_name.voided = 0
|
74
|
+
LEFT JOIN person_address ON person_address.person_id = patient.patient_id AND person_address.voided = 0
|
75
|
+
LEFT JOIN person_attribute landmark ON landmark.person_id = patient.patient_id
|
76
|
+
AND landmark.person_attribute_type_id = #{landmark_attribute_type_id}
|
77
|
+
WHERE patient.voided = 0
|
78
|
+
AND (patient_state.state != #{died_state_id}
|
79
|
+
OR (patient_state.state = #{transferred_out_state_id} AND patient_state.start_date < #{quoted_date}
|
80
|
+
AND (patient_state.end_date IS NULL OR patient_state.end_date > #{quoted_date})))
|
81
|
+
AND ((person.birthdate IS NULL OR TRIM(COALESCE(person.gender, '')) = '')
|
82
|
+
OR (TRIM(COALESCE(person_name.given_name, '')) = '' OR TRIM(COALESCE(person_name.family_name, '')) = '')
|
83
|
+
OR (TRIM(COALESCE(person_address.city_village, '')) = ''
|
84
|
+
OR TRIM(COALESCE(person_address.township_division, '')) = ''
|
85
|
+
OR TRIM(COALESCE(person_address.state_province, '')) = ''
|
86
|
+
OR TRIM(COALESCE(person_address.neighborhood_cell, '') = '')
|
87
|
+
OR TRIM(COALESCE(person_address.county_district, '') = '')
|
88
|
+
OR TRIM(COALESCE(person_address.address2, '') = '')
|
89
|
+
OR person_address.date_created < #{quoted_date}))
|
90
|
+
GROUP BY patient.patient_id
|
91
|
+
SQL
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Find all patients missing any of the following variables:
|
96
|
+
# - birthdate
|
97
|
+
# - gender
|
98
|
+
# - name
|
99
|
+
# - landmark
|
100
|
+
#
|
101
|
+
# NOTE: Old emastercard application did not collect a detailed and
|
102
|
+
# properly formatted address. The address was collected as free text and
|
103
|
+
# there was no standard provided for the address thus it's treated as
|
104
|
+
# a landmark by this system. Even for the new eMastercard application
|
105
|
+
# running atop this API, a free text alternative is provided if the
|
106
|
+
def emastercard_patients_missing_demographics
|
107
|
+
quoted_date = ActiveRecord::Base.connection.quote(date.to_s)
|
108
|
+
|
109
|
+
::Patient.find_by_sql(
|
110
|
+
<<~SQL
|
111
|
+
SELECT patient.patient_id AS patient_id,
|
112
|
+
patient_identifier.identifier AS arv_number,
|
113
|
+
person_name.given_name AS given_name,
|
114
|
+
person_name.family_name AS family_name,
|
115
|
+
person.birthdate AS birthdate,
|
116
|
+
person.gender AS gender,
|
117
|
+
person_address.city_village AS current_village,
|
118
|
+
person_address.township_division AS current_traditional_authority,
|
119
|
+
person_address.state_province AS current_district,
|
120
|
+
person_address.neighborhood_cell AS home_village,
|
121
|
+
person_address.county_district AS home_traditional_authority,
|
122
|
+
person_address.address2 AS home_district,
|
123
|
+
landmark.value AS landmark,
|
124
|
+
person_address.date_created AS address_last_updated_date
|
125
|
+
FROM patient
|
126
|
+
INNER JOIN person ON person.person_id = patient.patient_id AND person.voided = 0
|
127
|
+
INNER JOIN patient_program ON patient_program.patient_id = patient.patient_id AND patient_program.program_id = 1
|
128
|
+
INNER JOIN patient_state ON patient_state.patient_program_id = patient_program.patient_program_id
|
129
|
+
LEFT JOIN patient_identifier ON patient_identifier.patient_id = patient.patient_id
|
130
|
+
AND patient_identifier.voided = 0 AND patient_identifier.identifier_type = #{arv_number_id}
|
131
|
+
LEFT JOIN person_name ON person_name.person_id = patient.patient_id AND person_name.voided = 0
|
132
|
+
LEFT JOIN person_address ON person_address.person_id = patient.patient_id AND person_address.voided = 0
|
133
|
+
LEFT JOIN person_attribute landmark ON landmark.person_id = patient.patient_id
|
134
|
+
AND landmark.person_attribute_type_id = #{landmark_attribute_type_id}
|
135
|
+
WHERE patient.voided = 0
|
136
|
+
AND (patient_state.state != #{died_state_id}
|
137
|
+
OR (patient_state.state = #{transferred_out_state_id} AND patient_state.start_date < #{quoted_date}
|
138
|
+
AND (patient_state.end_date IS NULL OR patient_state.end_date > #{quoted_date})))
|
139
|
+
AND ((person.birthdate IS NULL OR TRIM(COALESCE(person.gender, '')) = '')
|
140
|
+
OR (TRIM(COALESCE(person_name.given_name, '')) = '' OR TRIM(COALESCE(person_name.family_name, '')) = '')
|
141
|
+
OR (TRIM(COALESCE(landmark.value, '')) = '' OR landmark.date_created < #{quoted_date}))
|
142
|
+
GROUP BY patient.patient_id
|
143
|
+
SQL
|
144
|
+
)
|
145
|
+
end
|
146
|
+
|
147
|
+
def arv_number_id
|
148
|
+
@arv_number_id ||= ::PatientIdentifierType.find_by(name: 'ARV Number').patient_identifier_type_id
|
149
|
+
end
|
150
|
+
|
151
|
+
def died_state_id
|
152
|
+
@died_state_id ||= ::ProgramWorkflowState.find_by_name_and_program(name: 'Died', program_id: 1).id
|
153
|
+
end
|
154
|
+
|
155
|
+
def transferred_out_state_id
|
156
|
+
@transferred_out_state_id ||= ::ProgramWorkflowState.find_by_name_and_program(name: 'Patient transferred out',
|
157
|
+
program_id: 1).id
|
158
|
+
end
|
159
|
+
|
160
|
+
def landmark_attribute_type_id
|
161
|
+
@landmark_attribute_type_id ||= ::PersonAttributeType.find_by_name('Landmark Or Plot Number').id
|
162
|
+
end
|
163
|
+
|
164
|
+
def parse_variant(variant)
|
165
|
+
variant = variant.downcase
|
166
|
+
|
167
|
+
return variant if %w[poc emastercard].include?(variant)
|
168
|
+
|
169
|
+
raise ::InvalidParameterError, "Invalid report variant '#{variant}'; expected poc or emastercard"
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Retrieve all pregnant females in a given time period
|
4
|
+
module MalawiHivProgramReports
|
5
|
+
module Clinic
|
6
|
+
class PregnantPatients
|
7
|
+
attr_reader :start_date, :end_date
|
8
|
+
|
9
|
+
include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
|
10
|
+
|
11
|
+
def initialize(start_date:, end_date:, **kwargs)
|
12
|
+
@start_date = start_date
|
13
|
+
@end_date = end_date
|
14
|
+
@occupation = kwargs[:occupation]
|
15
|
+
end
|
16
|
+
|
17
|
+
def find_report
|
18
|
+
all_pregnant.map do |patient|
|
19
|
+
{
|
20
|
+
patient_id: patient.patient_id,
|
21
|
+
arv_number: patient.arv_number,
|
22
|
+
given_name: patient.given_name,
|
23
|
+
family_name: patient.family_name,
|
24
|
+
gender: patient.gender,
|
25
|
+
birthdate: patient.birthdate,
|
26
|
+
last_reported_date: patient.last_reported_date
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def all_pregnant
|
34
|
+
str_start_date = ActiveRecord::Base.connection.quote(start_date)
|
35
|
+
str_end_date = ActiveRecord::Base.connection.quote(end_date)
|
36
|
+
|
37
|
+
::Person.find_by_sql(
|
38
|
+
<<~SQL
|
39
|
+
SELECT obs.person_id AS patient_id,
|
40
|
+
patient_identifier.identifier AS arv_number,
|
41
|
+
given_name,
|
42
|
+
family_name,
|
43
|
+
gender,
|
44
|
+
birthdate,
|
45
|
+
obs.obs_datetime AS last_reported_date
|
46
|
+
FROM obs
|
47
|
+
INNER JOIN person ON person.person_id = obs.person_id
|
48
|
+
INNER JOIN person_name ON person_name.person_id = obs.person_id
|
49
|
+
LEFT JOIN patient_identifier ON patient_identifier.patient_id = obs.person_id
|
50
|
+
AND patient_identifier.identifier_type = #{arv_number_type_id}
|
51
|
+
INNER JOIN encounter ON encounter.encounter_id = obs.encounter_id
|
52
|
+
AND encounter.program_id = #{hiv_program_id}
|
53
|
+
LEFT JOIN (#{current_occupation_query}) a ON a.person_id = obs.person_id
|
54
|
+
WHERE obs.concept_id IN (#{pregnant_concepts.select(:concept_id).to_sql})
|
55
|
+
AND obs.value_coded = #{yes_concept_id}
|
56
|
+
AND obs.person_id IN (#{patients_on_treatment.to_sql})
|
57
|
+
AND obs.obs_datetime = (
|
58
|
+
SELECT MAX(obs_date.obs_datetime) FROM obs obs_date
|
59
|
+
INNER JOIN encounter USING (encounter_id)
|
60
|
+
WHERE obs_date.concept_id IN (#{pregnant_concepts.select(:concept_id).to_sql})
|
61
|
+
AND DATE(obs_date.obs_datetime) BETWEEN #{str_start_date} AND #{str_end_date}
|
62
|
+
AND obs_date.person_id = obs.person_id
|
63
|
+
AND program_id = #{hiv_program_id}
|
64
|
+
) #{%w[Military Civilian].include?(@occupation) ? 'AND' : ''} #{occupation_filter(occupation: @occupation, field_name: 'value', table_name: 'a', include_clause: false)}
|
65
|
+
GROUP BY obs.person_id
|
66
|
+
SQL
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
def patients_on_treatment
|
71
|
+
PatientsOnTreatment.within(start_date, end_date)
|
72
|
+
end
|
73
|
+
|
74
|
+
def pregnant_concepts
|
75
|
+
::ConceptName.where(name: ['Is patient pregnant?', 'patient_pregnant'])
|
76
|
+
end
|
77
|
+
|
78
|
+
def yes_concept_id
|
79
|
+
::ConceptName.find_by_name('Yes').concept_id
|
80
|
+
end
|
81
|
+
|
82
|
+
def arv_number_type_id
|
83
|
+
::PatientIdentifierType.find_by_name('ARV Number').id
|
84
|
+
end
|
85
|
+
|
86
|
+
def hiv_program_id
|
87
|
+
::ArtService::Constants::PROGRAM_ID
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,282 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# this report is used to generate regimen data
|
4
|
+
module MalawiHivProgramReports
|
5
|
+
module Clinic
|
6
|
+
class RegimenDispensationData
|
7
|
+
attr_reader :start_date, :end_date, :type
|
8
|
+
include MalawiHivProgramReports::Utils::CommonSqlQueryUtils
|
9
|
+
include MalawiHivProgramReports::Adapters::Moh::Custom
|
10
|
+
include Utils
|
11
|
+
|
12
|
+
def initialize(start_date:, end_date:, **kwargs)
|
13
|
+
@start_date = ActiveRecord::Base.connection.quote(start_date.to_date.strftime('%Y-%m-%d 00:00:00'))
|
14
|
+
@end_date = ActiveRecord::Base.connection.quote(end_date.to_date.strftime('%Y-%m-%d 23:59:59'))
|
15
|
+
@type = kwargs[:type]
|
16
|
+
@occupation = kwargs[:occupation]
|
17
|
+
end
|
18
|
+
|
19
|
+
def find_report
|
20
|
+
drop_regimen_data
|
21
|
+
create_filtered_data
|
22
|
+
process_clients
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# rubocop:disable Metrics/AbcSize
|
28
|
+
def drop_regimen_data
|
29
|
+
ActiveRecord::Base.connection.execute 'DROP TABLE IF EXISTS temp_current_dispensation'
|
30
|
+
ActiveRecord::Base.connection.execute 'DROP TABLE IF EXISTS temp_drug_dispensed'
|
31
|
+
ActiveRecord::Base.connection.execute 'DROP TABLE IF EXISTS temp_current_regimen_names'
|
32
|
+
ActiveRecord::Base.connection.execute 'DROP TABLE IF EXISTS temp_current_patient_regimen'
|
33
|
+
ActiveRecord::Base.connection.execute 'DROP TABLE IF EXISTS temp_reg_outcome'
|
34
|
+
ActiveRecord::Base.connection.execute 'DROP TABLE IF EXISTS temp_vl_results'
|
35
|
+
ActiveRecord::Base.connection.execute 'DROP TABLE IF EXISTS temp_current_vl_results'
|
36
|
+
ActiveRecord::Base.connection.execute 'DROP TABLE IF EXISTS temp_regimen_patient_weight'
|
37
|
+
ActiveRecord::Base.connection.execute 'DROP TABLE IF EXISTS temp_regimen_data'
|
38
|
+
ActiveRecord::Base.connection.execute 'DROP TABLE IF EXISTS temp_patient_start_date'
|
39
|
+
end
|
40
|
+
# rubocop:enable Metrics/AbcSize
|
41
|
+
|
42
|
+
def create_filtered_data
|
43
|
+
create_temp_current_dispensation
|
44
|
+
create_temp_drug_dispensed
|
45
|
+
create_temp_current_regimen
|
46
|
+
create_temp_current_patient_regimen
|
47
|
+
create_temp_patient_outcome
|
48
|
+
create_temp_vl_result
|
49
|
+
create_temp_current_vl_results
|
50
|
+
create_temp_regimen_patient_weight
|
51
|
+
create_temp_patient_start_date
|
52
|
+
end
|
53
|
+
|
54
|
+
def create_temp_current_dispensation
|
55
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
56
|
+
CREATE TABLE temp_current_dispensation
|
57
|
+
SELECT o.patient_id, MAX(o.start_date) AS start_date
|
58
|
+
FROM orders o
|
59
|
+
INNER JOIN drug_order od ON od.order_id = o.order_id AND od.quantity > 0 AND od.drug_inventory_id IN (SELECT drug_id FROM arv_drug)
|
60
|
+
LEFT JOIN (#{current_occupation_query}) a ON a.person_id = o.patient_id
|
61
|
+
WHERE o.start_date > #{end_date} - INTERVAL 18 MONTH AND o.start_date < #{end_date} + INTERVAL 1 DAY
|
62
|
+
AND o.voided = 0 #{%w[Military Civilian].include?(@occupation) ? 'AND' : ''} #{occupation_filter(occupation: @occupation, field_name: 'value', table_name: 'a', include_clause: false)}
|
63
|
+
AND o.order_type_id = 1 -- drug order
|
64
|
+
GROUP BY o.patient_id
|
65
|
+
SQL
|
66
|
+
ActiveRecord::Base.connection.execute 'create index current_disp on temp_current_dispensation (patient_id, start_date)'
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_temp_drug_dispensed
|
70
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
71
|
+
CREATE table temp_drug_dispensed
|
72
|
+
SELECT o.patient_id, od.drug_inventory_id, d.name, o.start_date, od.quantity
|
73
|
+
FROM orders o
|
74
|
+
INNER JOIN temp_current_dispensation tcd ON tcd.patient_id = o.patient_id AND tcd.start_date = o.start_date
|
75
|
+
INNER JOIN drug_order od ON od.order_id = o.order_id AND od.quantity > 0 AND od.drug_inventory_id IN (SELECT drug_id FROM arv_drug)
|
76
|
+
INNER JOIN drug d ON d.drug_id = od.drug_inventory_id AND d.retired = 0
|
77
|
+
WHERE o.voided = 0
|
78
|
+
AND o.order_type_id = 1 -- drug order
|
79
|
+
SQL
|
80
|
+
ActiveRecord::Base.connection.execute 'create index drug_disp on temp_drug_dispensed (patient_id, start_date)'
|
81
|
+
end
|
82
|
+
|
83
|
+
def create_temp_current_regimen
|
84
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
85
|
+
CREATE table temp_current_regimen_names
|
86
|
+
SELECT GROUP_CONCAT(drug.drug_id ORDER BY drug.drug_id ASC) AS drugs, regimen_name.name AS name
|
87
|
+
FROM moh_regimen_combination AS combo
|
88
|
+
INNER JOIN moh_regimen_combination_drug AS drug USING (regimen_combination_id)
|
89
|
+
INNER JOIN moh_regimen_name AS regimen_name USING (regimen_name_id)
|
90
|
+
GROUP BY combo.regimen_combination_id
|
91
|
+
SQL
|
92
|
+
ActiveRecord::Base.connection.execute 'create index regimen_names on temp_current_regimen_names (drugs(50))'
|
93
|
+
end
|
94
|
+
|
95
|
+
def create_temp_current_patient_regimen
|
96
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
97
|
+
CREATE table temp_current_patient_regimen
|
98
|
+
SELECT patient_id, name
|
99
|
+
FROM temp_current_regimen_names tcrn
|
100
|
+
INNER JOIN (
|
101
|
+
SELECT patient_id, GROUP_CONCAT(drug_inventory_id ORDER BY drug_inventory_id ASC) AS drugs
|
102
|
+
FROM temp_drug_dispensed
|
103
|
+
GROUP BY patient_id
|
104
|
+
) d ON d.drugs = tcrn.drugs
|
105
|
+
SQL
|
106
|
+
ActiveRecord::Base.connection.execute 'create index patient_regimen on temp_current_patient_regimen (patient_id)'
|
107
|
+
end
|
108
|
+
|
109
|
+
def create_temp_patient_outcome
|
110
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
111
|
+
CREATE TABLE temp_reg_outcome
|
112
|
+
SELECT patient_id, #{type == 'pepfar' ? "pepfar_patient_outcome(patient_id, #{end_date})" : "patient_outcome(patient_id, #{end_date})"} outcome
|
113
|
+
FROM temp_current_dispensation
|
114
|
+
SQL
|
115
|
+
ActiveRecord::Base.connection.execute 'create index reg_outcome on temp_reg_outcome (patient_id)'
|
116
|
+
end
|
117
|
+
|
118
|
+
def create_temp_vl_result
|
119
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
120
|
+
CREATE table temp_vl_results
|
121
|
+
SELECT
|
122
|
+
lab_result_obs.obs_datetime AS result_date,
|
123
|
+
CONCAT (COALESCE(measure.value_modifier, '='),' ',COALESCE(measure.value_numeric, measure.value_text, '')) AS result,
|
124
|
+
lab_result_obs.person_id AS patient_id
|
125
|
+
FROM obs AS lab_result_obs
|
126
|
+
INNER JOIN orders ON orders.order_id = lab_result_obs.order_id AND orders.voided = 0
|
127
|
+
INNER JOIN obs AS measure ON measure.obs_group_id = lab_result_obs.obs_id AND measure.voided = 0
|
128
|
+
INNER JOIN (
|
129
|
+
SELECT concept_id, name
|
130
|
+
FROM concept_name
|
131
|
+
INNER JOIN concept USING (concept_id)
|
132
|
+
WHERE concept.retired = 0
|
133
|
+
AND name NOT LIKE 'Lab test result'
|
134
|
+
GROUP BY concept_id
|
135
|
+
) AS measure_concept ON measure_concept.concept_id = measure.concept_id
|
136
|
+
WHERE lab_result_obs.voided = 0
|
137
|
+
AND measure.person_id IN (SELECT patient_id FROM temp_reg_outcome WHERE outcome = 'On antiretrovirals')
|
138
|
+
AND (measure.value_numeric IS NOT NULL || measure.value_text IS NOT NULL)
|
139
|
+
AND lab_result_obs.obs_datetime < #{end_date} + INTERVAL 1 DAY
|
140
|
+
ORDER BY lab_result_obs.obs_datetime DESC
|
141
|
+
SQL
|
142
|
+
ActiveRecord::Base.connection.execute 'create index vl_result on temp_vl_results (patient_id, result_date)'
|
143
|
+
end
|
144
|
+
|
145
|
+
def create_temp_current_vl_results
|
146
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
147
|
+
CREATE TABLE temp_current_vl_results
|
148
|
+
SELECT t.*
|
149
|
+
FROM temp_vl_results t
|
150
|
+
LEFT JOIN temp_vl_results td ON td.patient_id = t.patient_id AND td.result_date > t.result_date
|
151
|
+
WHERE td.patient_id IS NULL
|
152
|
+
SQL
|
153
|
+
ActiveRecord::Base.connection.execute 'create index current_vl on temp_current_vl_results (patient_id)'
|
154
|
+
end
|
155
|
+
|
156
|
+
def create_temp_regimen_patient_weight
|
157
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
158
|
+
CREATE TABLE temp_regimen_patient_weight
|
159
|
+
SELECT tro.patient_id, o.value_numeric AS weight
|
160
|
+
FROM temp_reg_outcome tro
|
161
|
+
INNER JOIN (
|
162
|
+
SELECT person_id, MAX(obs_datetime) AS max_datetime
|
163
|
+
FROM obs
|
164
|
+
WHERE concept_id = 5089 AND voided = 0 AND obs_datetime < #{end_date} + INTERVAL 1 DAY
|
165
|
+
GROUP BY person_id
|
166
|
+
) latest_obs ON latest_obs.person_id = tro.patient_id
|
167
|
+
INNER JOIN obs o ON o.person_id = latest_obs.person_id AND o.concept_id = 5089 AND o.obs_datetime = latest_obs.max_datetime AND o.voided = 0 AND o.obs_datetime < #{end_date} + INTERVAL 1 DAY
|
168
|
+
WHERE tro.outcome = 'On antiretrovirals'
|
169
|
+
SQL
|
170
|
+
ActiveRecord::Base.connection.execute 'create index patient_weight on temp_regimen_patient_weight (patient_id)'
|
171
|
+
end
|
172
|
+
|
173
|
+
def create_temp_patient_start_date
|
174
|
+
ActiveRecord::Base.connection.execute <<~SQL
|
175
|
+
CREATE TABLE temp_patient_start_date
|
176
|
+
SELECT
|
177
|
+
`p`.`patient_id` AS `patient_id`,
|
178
|
+
cast(patient_date_enrolled(`p`.`patient_id`) as date) AS `date_enrolled`,
|
179
|
+
date_antiretrovirals_started(`p`.`patient_id`, min(`s`.`start_date`)) AS `earliest_start_date`
|
180
|
+
FROM
|
181
|
+
((`patient_program` `p`
|
182
|
+
LEFT JOIN `person` `pe` ON ((`pe`.`person_id` = `p`.`patient_id`))
|
183
|
+
LEFT JOIN `patient_state` `s` ON ((`p`.`patient_program_id` = `s`.`patient_program_id`)))
|
184
|
+
LEFT JOIN `person` ON ((`person`.`person_id` = `p`.`patient_id`)))
|
185
|
+
WHERE
|
186
|
+
((`p`.`voided` = 0)
|
187
|
+
AND (`s`.`voided` = 0)
|
188
|
+
AND (`p`.`program_id` = 1)
|
189
|
+
AND (`s`.`state` = 7))
|
190
|
+
AND (`s`.`start_date` < #{end_date} + INTERVAL 1 DAY)
|
191
|
+
AND p.patient_id IN (SELECT patient_id FROM temp_reg_outcome WHERE outcome = 'On antiretrovirals')
|
192
|
+
GROUP BY `p`.`patient_id`;
|
193
|
+
SQL
|
194
|
+
ActiveRecord::Base.connection.execute 'create index start_date on temp_patient_start_date (patient_id)'
|
195
|
+
end
|
196
|
+
|
197
|
+
def clients_alive_on_treatment
|
198
|
+
ActiveRecord::Base.connection.select_all <<~SQL
|
199
|
+
SELECT
|
200
|
+
trc.patient_id,
|
201
|
+
p.gender,
|
202
|
+
p.birthdate,
|
203
|
+
pn.given_name,
|
204
|
+
pn.family_name,
|
205
|
+
i.identifier arv_number,
|
206
|
+
tpw.weight,
|
207
|
+
tcvr.result_date,
|
208
|
+
tcvr.result,
|
209
|
+
tcp.name regimen,
|
210
|
+
trc.earliest_start_date
|
211
|
+
FROM temp_patient_start_date trc
|
212
|
+
INNER JOIN temp_current_dispensation tcd ON tcd.patient_id = trc.patient_id AND tcd.start_date BETWEEN #{@start_date} AND #{@end_date}
|
213
|
+
INNER JOIN person p ON p.person_id = trc.patient_id AND p.voided = 0
|
214
|
+
INNER JOIN person_name pn ON pn.person_id = p.person_id AND pn.voided = 0
|
215
|
+
LEFT JOIN patient_identifier i ON i.patient_id = p.person_id AND i.identifier_type = 4 AND i.voided = 0
|
216
|
+
LEFT JOIN temp_regimen_patient_weight tpw ON tpw.patient_id = trc.patient_id
|
217
|
+
LEFT JOIN temp_current_vl_results tcvr ON tcvr.patient_id = trc.patient_id
|
218
|
+
LEFT JOIN temp_current_patient_regimen tcp ON tcp.patient_id = trc.patient_id
|
219
|
+
GROUP BY trc.patient_id
|
220
|
+
SQL
|
221
|
+
end
|
222
|
+
|
223
|
+
def alive_clients
|
224
|
+
clients = ActiveRecord::Base.connection.select_all <<~SQL
|
225
|
+
SELECT trc.patient_id
|
226
|
+
FROM temp_reg_outcome trc
|
227
|
+
INNER JOIN person p ON p.person_id = trc.patient_id AND p.voided = 0
|
228
|
+
where trc.outcome = 'On antiretrovirals' AND LEFT(p.gender, 1) = 'F'
|
229
|
+
SQL
|
230
|
+
clients.map { |client| client['patient_id'] }
|
231
|
+
end
|
232
|
+
|
233
|
+
def process_clients
|
234
|
+
clients = {}
|
235
|
+
@maternal_status = maternal_status
|
236
|
+
clients_alive_on_treatment.each do |client|
|
237
|
+
clients[client['patient_id']] = {
|
238
|
+
arv_number: client['arv_number'],
|
239
|
+
given_name: client['given_name'],
|
240
|
+
family_name: client['family_name'],
|
241
|
+
birthdate: client['birthdate'],
|
242
|
+
gender: client['gender'] == 'M' ? 'M' : fetch_maternal_status(client['patient_id']),
|
243
|
+
current_regimen: client['regimen'],
|
244
|
+
current_weight: client['weight'],
|
245
|
+
art_start_date: client['earliest_start_date'],
|
246
|
+
medication: fetch_medication(client['patient_id']),
|
247
|
+
vl_result: client['result'],
|
248
|
+
vl_result_date: client['result_date']
|
249
|
+
}
|
250
|
+
end
|
251
|
+
clients
|
252
|
+
end
|
253
|
+
|
254
|
+
def maternal_status
|
255
|
+
MalawiHivProgramReports::Pepfar::ViralLoadCoverage2.new(start_date: @start_date,
|
256
|
+
end_date: @end_date).vl_maternal_status(alive_clients)
|
257
|
+
end
|
258
|
+
|
259
|
+
def fetch_maternal_status(patient_id)
|
260
|
+
return nil if patient_id.blank?
|
261
|
+
|
262
|
+
gender = 'FNP'
|
263
|
+
gender = 'FP' if @maternal_status[:FP].include?(patient_id)
|
264
|
+
gender = 'FBf' if @maternal_status[:FBf].include?(patient_id)
|
265
|
+
gender
|
266
|
+
end
|
267
|
+
|
268
|
+
def fetch_medication(patient_id)
|
269
|
+
result = []
|
270
|
+
data = ActiveRecord::Base.connection.select_all <<~SQL
|
271
|
+
SELECT tdd.name, DATE(tdd.start_date) start_date, tdd.quantity
|
272
|
+
FROM temp_drug_dispensed tdd
|
273
|
+
WHERE tdd.patient_id = #{patient_id}
|
274
|
+
SQL
|
275
|
+
data.each do |row|
|
276
|
+
result << { medication: row['name'], start_date: row['start_date'], quantity: row['quantity'] }
|
277
|
+
end
|
278
|
+
result
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|