diff --git a/app/controllers/api/v3/medical_histories_controller.rb b/app/controllers/api/v3/medical_histories_controller.rb index b14e7f7d1c..a9c99edc1c 100644 --- a/app/controllers/api/v3/medical_histories_controller.rb +++ b/app/controllers/api/v3/medical_histories_controller.rb @@ -24,7 +24,12 @@ def merge_if_valid(medical_history_params) .merge(metadata) medical_history = MedicalHistory.merge(record_params) - {record: medical_history} + + if medical_history.errors.any? + {errors_hash: errors_hash(medical_history)} + else + {record: medical_history} + end end end @@ -48,6 +53,8 @@ def medical_histories_params :smoking, :smokeless_tobacco, :cholesterol, + :htn_diagnosed_at, + :dm_diagnosed_at, :created_at, :updated_at ) diff --git a/app/models/medical_history.rb b/app/models/medical_history.rb index ec82728e15..078f1a10b3 100644 --- a/app/models/medical_history.rb +++ b/app/models/medical_history.rb @@ -1,5 +1,6 @@ class MedicalHistory < ApplicationRecord include Mergeable + belongs_to :patient, optional: true belongs_to :user, optional: true @@ -29,15 +30,110 @@ class MedicalHistory < ApplicationRecord enum chronic_kidney_disease: MEDICAL_HISTORY_ANSWERS, _prefix: true enum receiving_treatment_for_hypertension: MEDICAL_HISTORY_ANSWERS, _prefix: true enum receiving_treatment_for_diabetes: MEDICAL_HISTORY_ANSWERS, _prefix: true - enum diabetes: MEDICAL_HISTORY_ANSWERS, _prefix: true - enum hypertension: MEDICAL_HISTORY_ANSWERS, _prefix: true - enum diagnosed_with_hypertension: MEDICAL_HISTORY_ANSWERS, _prefix: true enum smoking: MEDICAL_HISTORY_ANSWERS, _prefix: true enum smokeless_tobacco: MEDICAL_HISTORY_ANSWERS, _prefix: true + enum diabetes: MEDICAL_HISTORY_ANSWERS.merge(suspected: "suspected"), _prefix: true + enum hypertension: MEDICAL_HISTORY_ANSWERS.merge(suspected: "suspected"), _prefix: true + enum diagnosed_with_hypertension: MEDICAL_HISTORY_ANSWERS.merge(suspected: "suspected"), _prefix: true scope :for_sync, -> { with_discarded } + before_validation :backfill_diagnosed_dates + before_validation :silently_enforce_medical_history_rules + + after_save :update_patient_diagnosed_confirmed_at + def indicates_hypertension_risk? prior_heart_attack_yes? || prior_stroke_yes? end + + private + + def silently_enforce_medical_history_rules + enforce_date_rules_silently + enforce_one_way_enums_silently + end + + def backfill_diagnosed_dates + return if htn_diagnosed_at.present? || dm_diagnosed_at.present? + return if hypertension_suspected? || diabetes_suspected? + + source = patient&.recorded_at + return unless source.present? + + if %w[yes no].include?(hypertension&.to_s) + self.htn_diagnosed_at ||= source + end + + if %w[yes no].include?(diabetes&.to_s) + self.dm_diagnosed_at ||= source + end + end + + def enforce_one_way_enums_silently + %i[hypertension diabetes diagnosed_with_hypertension].each do |attr| + prev = send("#{attr}_was") + curr = send(attr) + + next if prev.blank? || prev.to_s == curr.to_s + + prev_s = prev.to_s + curr_s = curr.to_s + + if prev_s == "suspected" + next + end + + if %w[yes no].include?(prev_s) && curr_s == "suspected" + write_attribute(attr, prev_s) + + case attr + when :hypertension + write_attribute(:htn_diagnosed_at, htn_diagnosed_at_was) + when :diabetes + write_attribute(:dm_diagnosed_at, dm_diagnosed_at_was) + end + + Rails.logger.info( + "[MedicalHistory] Prevented change of #{attr} from #{curr_s} -> #{prev_s} for medical_history_id=#{id || "new"}" + ) + next + end + end + end + + def enforce_date_rules_silently + self.htn_diagnosed_at = nil unless %w[yes no].include?(hypertension&.to_s) + self.dm_diagnosed_at = nil unless %w[yes no].include?(diabetes&.to_s) + + if htn_diagnosed_at_was.present? && !timestamps_equal?(htn_diagnosed_at, htn_diagnosed_at_was) + write_attribute(:htn_diagnosed_at, htn_diagnosed_at_was) + Rails.logger.info("[MedicalHistory] Silently preserved existing htn_diagnosed_at for id=#{id || "new"}") + end + + if dm_diagnosed_at_was.present? && !timestamps_equal?(dm_diagnosed_at, dm_diagnosed_at_was) + write_attribute(:dm_diagnosed_at, dm_diagnosed_at_was) + Rails.logger.info("[MedicalHistory] Silently preserved existing dm_diagnosed_at for id=#{id || "new"}") + end + end + + def update_patient_diagnosed_confirmed_at + return unless patient + return if patient.diagnosed_confirmed_at.present? + + valid_htn_date = htn_diagnosed_at.present? && %w[yes no].include?(hypertension&.to_s) + valid_dm_date = dm_diagnosed_at.present? && %w[yes no].include?(diabetes&.to_s) + + if valid_htn_date || valid_dm_date + earliest = [ + (htn_diagnosed_at if valid_htn_date), + (dm_diagnosed_at if valid_dm_date) + ].compact.min + patient.update_columns(diagnosed_confirmed_at: earliest) + end + end + + def timestamps_equal?(a, b) + a&.change(usec: 0) == b&.change(usec: 0) + end end diff --git a/app/schema/api/v3/models.rb b/app/schema/api/v3/models.rb index 977e2767f1..711582aff0 100644 --- a/app/schema/api/v3/models.rb +++ b/app/schema/api/v3/models.rb @@ -304,12 +304,14 @@ def medical_history chronic_kidney_disease: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys}, receiving_treatment_for_hypertension: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys}, receiving_treatment_for_diabetes: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys}, - diabetes: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys}, - hypertension: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys}, - diagnosed_with_hypertension: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys}, + diabetes: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys + ["suspected"]}, + hypertension: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys + ["suspected"]}, + diagnosed_with_hypertension: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys + ["suspected"]}, smoking: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys}, smokeless_tobacco: {type: :string, enum: MedicalHistory::MEDICAL_HISTORY_ANSWERS.keys}, cholesterol_value: {type: :number}, + htn_diagnosed_at: {"$ref" => "#/definitions/nullable_timestamp"}, + dm_diagnosed_at: {"$ref" => "#/definitions/nullable_timestamp"}, deleted_at: {"$ref" => "#/definitions/nullable_timestamp"}, created_at: {"$ref" => "#/definitions/timestamp"}, updated_at: {"$ref" => "#/definitions/timestamp"} diff --git a/app/validators/api/v3/medical_history_payload_validator.rb b/app/validators/api/v3/medical_history_payload_validator.rb index ff43d4ac34..cec326e0f3 100644 --- a/app/validators/api/v3/medical_history_payload_validator.rb +++ b/app/validators/api/v3/medical_history_payload_validator.rb @@ -13,6 +13,8 @@ class Api::V3::MedicalHistoryPayloadValidator < Api::V3::PayloadValidator :smoking, :smokeless_tobacco, :cholesterol, + :htn_diagnosed_at, + :dm_diagnosed_at, :created_at, :updated_at ) diff --git a/app/validators/api/v3/patient_payload_validator.rb b/app/validators/api/v3/patient_payload_validator.rb index 298206fb8b..84401d0764 100644 --- a/app/validators/api/v3/patient_payload_validator.rb +++ b/app/validators/api/v3/patient_payload_validator.rb @@ -23,6 +23,7 @@ class Api::V3::PatientPayloadValidator < Api::V3::PayloadValidator :reminder_consent, :deleted_reason, :skip_facility_authorization, + :diagnosed_confirmed_at, :eligible_for_reassignment ) diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb index fae70e7d51..b30474555a 100644 --- a/config/initializers/filter_parameter_logging.rb +++ b/config/initializers/filter_parameter_logging.rb @@ -22,6 +22,7 @@ module ParameterFiltering status updated_at v2 + diagnosed_confirmed_at ].freeze ALLOWED_REGEX = /(^|_)ids?|#{Regexp.union(ALLOWED_ATTRIBUTES)}/.freeze diff --git a/config/schema_descriptions.yml b/config/schema_descriptions.yml index f34915d795..3e0e3d7264 100644 --- a/config/schema_descriptions.yml +++ b/config/schema_descriptions.yml @@ -194,6 +194,7 @@ reporting_patient_states: diabetes_treatment_outcome_in_last_2_months: "For the visiting period of the last 2 months, is this patient's diabetes treatment outcome bs_under_200, bs_200_to_300, bs_over_300, missed_visit, or visited_no_bp?" diabetes_treatment_outcome_in_quarter: "For the visiting period of the current quarter, is this patient's diabetes treatment outcome bs_under_200, bs_200_to_300, bs_over_300, missed_visit, or visited_no_bp?" titrated: "True, if the patient had an increase in dosage of any hypertension drug in a visit this month." + diagnosed_confirmed_at: "Time (in UTC) at which the patient had it's first diagnosis completed, also differentiating him from screened patients." reporting_facilities: description: "List of Simple facililities with size, type, and geographical information. These facilities are used to segment reports by region." columns: diff --git a/db/migrate/20251112085230_add_diagnosed_confirmed_at_to_patients.rb b/db/migrate/20251112085230_add_diagnosed_confirmed_at_to_patients.rb new file mode 100644 index 0000000000..bdcf43456b --- /dev/null +++ b/db/migrate/20251112085230_add_diagnosed_confirmed_at_to_patients.rb @@ -0,0 +1,16 @@ +class AddDiagnosedConfirmedAtToPatients < ActiveRecord::Migration[6.1] + def up + add_column :patients, :diagnosed_confirmed_at, :datetime + + execute <<~SQL.squish + UPDATE patients + SET diagnosed_confirmed_at = recorded_at + WHERE diagnosed_confirmed_at IS NULL + AND recorded_at IS NOT NULL; + SQL + end + + def down + remove_column :patients, :diagnosed_confirmed_at + end +end diff --git a/db/migrate/20251112091000_add_diagnosis_timestamps_to_medical_histories.rb b/db/migrate/20251112091000_add_diagnosis_timestamps_to_medical_histories.rb new file mode 100644 index 0000000000..e29a03a23a --- /dev/null +++ b/db/migrate/20251112091000_add_diagnosis_timestamps_to_medical_histories.rb @@ -0,0 +1,27 @@ +class AddDiagnosisTimestampsToMedicalHistories < ActiveRecord::Migration[6.1] + def up + add_column :medical_histories, :htn_diagnosed_at, :datetime + add_column :medical_histories, :dm_diagnosed_at, :datetime + + say_with_time "Backfilling diagnosis timestamps for existing medical histories" do + MedicalHistory.includes(:patient).find_each(batch_size: 1000) do |mh| + next unless mh.patient + + htn_time = mh.diagnosed_with_hypertension == "yes" || mh.hypertension == "yes" ? mh.patient.recorded_at : nil + dm_time = mh.diabetes == "yes" ? mh.patient.recorded_at : nil + + next unless htn_time || dm_time + + mh.update_columns( + htn_diagnosed_at: htn_time, + dm_diagnosed_at: dm_time + ) + end + end + end + + def down + remove_column :medical_histories, :htn_diagnosed_at + remove_column :medical_histories, :dm_diagnosed_at + end +end diff --git a/db/migrate/20251119095624_remove_screened_patients_from_reporting_patient_states.rb b/db/migrate/20251119095624_remove_screened_patients_from_reporting_patient_states.rb new file mode 100644 index 0000000000..1314d63771 --- /dev/null +++ b/db/migrate/20251119095624_remove_screened_patients_from_reporting_patient_states.rb @@ -0,0 +1,441 @@ +class RemoveScreenedPatientsFromReportingPatientStates < ActiveRecord::Migration[6.1] + def up + execute <<~SQL + ALTER TABLE simple_reporting.reporting_patient_states ADD COLUMN diagnosed_confirmed_at timestamp without time zone; + SQL + + execute <<~SQL + UPDATE simple_reporting.reporting_patient_states SET diagnosed_confirmed_at = recorded_at + WHERE diagnosed_confirmed_at IS NULL AND recorded_at IS NOT NULL; + SQL + + execute <<~SQL + CREATE OR REPLACE FUNCTION simple_reporting.reporting_patient_states_table_function(date) RETURNS SETOF simple_reporting.reporting_patient_states + LANGUAGE plpgsql + AS $_$ + BEGIN + RETURN QUERY + SELECT DISTINCT ON (p.id) + -- Basic patient identifiers + p.id AS patient_id, + p.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE 'UTC' AS recorded_at, + p.status, + p.gender, + p.age, + p.age_updated_at AT TIME ZONE 'UTC' AT TIME ZONE 'UTC' AS age_updated_at, + p.date_of_birth, + EXTRACT(YEAR FROM COALESCE( + age(p.date_of_birth), + make_interval(years => p.age) + age(p.age_updated_at) + ))::float8 AS current_age, + + -- Calendar + cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string, + + -- Medical history + mh.hypertension, + mh.prior_heart_attack, + mh.prior_stroke, + mh.chronic_kidney_disease, + mh.receiving_treatment_for_hypertension, + mh.diabetes, + + -- Assigned facility and regions + p.assigned_facility_id, + assigned_facility.facility_size, + assigned_facility.facility_type, + assigned_facility.facility_region_slug, + assigned_facility.facility_region_id, + assigned_facility.block_slug, + assigned_facility.block_region_id, + assigned_facility.district_slug, + assigned_facility.district_region_id, + assigned_facility.state_slug, + assigned_facility.state_region_id, + assigned_facility.organization_slug, + assigned_facility.organization_region_id, + + -- Registration facility and regions + p.registration_facility_id, + registration_facility.facility_size, + registration_facility.facility_type, + registration_facility.facility_region_slug, + registration_facility.facility_region_id, + registration_facility.block_slug, + registration_facility.block_region_id, + registration_facility.district_slug, + registration_facility.district_region_id, + registration_facility.state_slug, + registration_facility.state_region_id, + registration_facility.organization_slug, + registration_facility.organization_region_id, + + -- Visit details + bps.blood_pressure_id, + bps.blood_pressure_facility_id AS bp_facility_id, + bps.blood_pressure_recorded_at AS bp_recorded_at, + bps.systolic, + bps.diastolic, + + bss.blood_sugar_id, + bss.blood_sugar_facility_id AS bs_facility_id, + bss.blood_sugar_recorded_at AS bs_recorded_at, + bss.blood_sugar_type, + bss.blood_sugar_value, + bss.blood_sugar_risk_state, + + visits.encounter_id, + visits.encounter_recorded_at, + visits.prescription_drug_id, + visits.prescription_drug_recorded_at, + visits.appointment_id, + visits.appointment_recorded_at, + visits.visited_facility_ids, + + -- Relative time calculations + (cal.year - DATE_PART('year', p.diagnosed_confirmed_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) * 12 + + (cal.month - DATE_PART('month', p.diagnosed_confirmed_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) + AS months_since_registration, + + (cal.year - DATE_PART('year', p.diagnosed_confirmed_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) * 4 + + (cal.quarter - DATE_PART('quarter', p.diagnosed_confirmed_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) + AS quarters_since_registration, + + visits.months_since_visit, + visits.quarters_since_visit, + bps.months_since_bp, + bps.quarters_since_bp, + bss.months_since_bs, + bss.quarters_since_bs, + + -- BP and treatment indicators + CASE + WHEN bps.systolic IS NULL OR bps.diastolic IS NULL THEN 'unknown' + WHEN bps.systolic < 140 AND bps.diastolic < 90 THEN 'controlled' + ELSE 'uncontrolled' + END AS last_bp_state, + + CASE + WHEN p.status = 'dead' THEN 'dead' + WHEN ( + (cal.year - DATE_PART('year', p.diagnosed_confirmed_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) * 12 + + (cal.month - DATE_PART('month', p.diagnosed_confirmed_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) < 12 + OR visits.months_since_visit < 12 + ) THEN 'under_care' + ELSE 'lost_to_follow_up' + END AS htn_care_state, + + CASE + WHEN visits.months_since_visit >= 3 OR visits.months_since_visit IS NULL THEN 'missed_visit' + WHEN bps.months_since_bp >= 3 OR bps.months_since_bp IS NULL THEN 'visited_no_bp' + WHEN bps.systolic < 140 AND bps.diastolic < 90 THEN 'controlled' + ELSE 'uncontrolled' + END AS htn_treatment_outcome_in_last_3_months, + + CASE + WHEN visits.months_since_visit >= 2 OR visits.months_since_visit IS NULL THEN 'missed_visit' + WHEN bps.months_since_bp >= 2 OR bps.months_since_bp IS NULL THEN 'visited_no_bp' + WHEN bps.systolic < 140 AND bps.diastolic < 90 THEN 'controlled' + ELSE 'uncontrolled' + END AS htn_treatment_outcome_in_last_2_months, + + CASE + WHEN visits.quarters_since_visit >= 1 OR visits.quarters_since_visit IS NULL THEN 'missed_visit' + WHEN bps.quarters_since_bp >= 1 OR bps.quarters_since_bp IS NULL THEN 'visited_no_bp' + WHEN bps.systolic < 140 AND bps.diastolic < 90 THEN 'controlled' + ELSE 'uncontrolled' + END AS htn_treatment_outcome_in_quarter, + + CASE + WHEN (visits.months_since_visit >= 3 OR visits.months_since_visit is NULL) THEN 'missed_visit' + WHEN (bss.months_since_bs >= 3 OR bss.months_since_bs is NULL) THEN 'visited_no_bs' + ELSE bss.blood_sugar_risk_state + END AS diabetes_treatment_outcome_in_last_3_months, + + CASE + WHEN visits.months_since_visit >= 2 OR visits.months_since_visit IS NULL THEN 'missed_visit' + WHEN bss.months_since_bs >= 2 OR bss.months_since_bs IS NULL THEN 'visited_no_bs' + ELSE bss.blood_sugar_risk_state + END AS diabetes_treatment_outcome_in_last_2_months, + + CASE + WHEN visits.quarters_since_visit >= 1 OR visits.quarters_since_visit IS NULL THEN 'missed_visit' + WHEN bss.quarters_since_bs >= 1 OR bss.quarters_since_bs IS NULL THEN 'visited_no_bs' + ELSE bss.blood_sugar_risk_state + END AS diabetes_treatment_outcome_in_quarter, + + ( + current_meds.amlodipine > past_meds.amlodipine OR + current_meds.telmisartan > past_meds.telmisartan OR + current_meds.losartan > past_meds.losartan OR + current_meds.atenolol > past_meds.atenolol OR + current_meds.enalapril > past_meds.enalapril OR + current_meds.chlorthalidone > past_meds.chlorthalidone OR + current_meds.hydrochlorothiazide > past_meds.hydrochlorothiazide + ) AS titrated, + p.diagnosed_confirmed_at AT TIME ZONE 'UTC' AT TIME ZONE 'UTC' AS diagnosed_confirmed_at + + FROM public.patients p + JOIN public.reporting_months cal + ON cal.month_date = $1 + AND p.diagnosed_confirmed_at <= cal.month_date + INTERVAL '1 month' + INTERVAL '1 day' + AND (( + to_char(timezone((SELECT current_setting('TIMEZONE'::text) AS current_setting), TIMEZONE('UTC'::text, p.diagnosed_confirmed_at)), 'YYYY-MM'::text) <= + to_char((cal.month_date)::timestamp with time zone, 'YYYY-MM'::text)) + ) + + LEFT OUTER JOIN public.reporting_patient_blood_pressures bps + ON p.id = bps.patient_id AND cal.month_date = bps.month_date + + LEFT OUTER JOIN public.reporting_patient_blood_sugars bss + ON p.id = bss.patient_id AND cal.month_date = bss.month_date + + LEFT OUTER JOIN public.reporting_patient_visits visits + ON p.id = visits.patient_id AND cal.month_date = visits.month_date + + LEFT OUTER JOIN LATERAL ( + SELECT DISTINCT ON (patient_id) * + FROM public.medical_histories + WHERE patient_id = p.id AND deleted_at IS NULL + ORDER BY patient_id, device_updated_at DESC + ) mh ON true + + LEFT OUTER JOIN public.reporting_prescriptions current_meds + ON current_meds.patient_id = p.id AND cal.month_date = current_meds.month_date + + LEFT OUTER JOIN public.reporting_prescriptions past_meds + ON past_meds.patient_id = p.id AND cal.month_date = past_meds.month_date + INTERVAL '1 month' + + INNER JOIN public.reporting_facilities registration_facility + ON registration_facility.facility_id = p.registration_facility_id + + INNER JOIN public.reporting_facilities assigned_facility + ON assigned_facility.facility_id = p.assigned_facility_id + + WHERE p.deleted_at IS NULL + AND p.diagnosed_confirmed_at IS NOT NULL + ORDER BY p.id; + END; + $_$; + SQL + end + + def down + execute <<~SQL + ALTER TABLE simple_reporting.reporting_patient_states DROP COLUMN diagnosed_confirmed_at; + SQL + + execute <<~SQL + CREATE OR REPLACE FUNCTION simple_reporting.reporting_patient_states_table_function(date) RETURNS SETOF simple_reporting.reporting_patient_states + LANGUAGE plpgsql + AS $_$ + BEGIN + RETURN QUERY + SELECT DISTINCT ON (p.id) + -- Basic patient identifiers + p.id AS patient_id, + p.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE 'UTC' AS recorded_at, + p.status, + p.gender, + p.age, + p.age_updated_at AT TIME ZONE 'UTC' AT TIME ZONE 'UTC' AS age_updated_at, + p.date_of_birth, + EXTRACT(YEAR FROM COALESCE( + age(p.date_of_birth), + make_interval(years => p.age) + age(p.age_updated_at) + ))::float8 AS current_age, + + -- Calendar + cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string, + + -- Medical history + mh.hypertension, + mh.prior_heart_attack, + mh.prior_stroke, + mh.chronic_kidney_disease, + mh.receiving_treatment_for_hypertension, + mh.diabetes, + + -- Assigned facility and regions + p.assigned_facility_id, + assigned_facility.facility_size, + assigned_facility.facility_type, + assigned_facility.facility_region_slug, + assigned_facility.facility_region_id, + assigned_facility.block_slug, + assigned_facility.block_region_id, + assigned_facility.district_slug, + assigned_facility.district_region_id, + assigned_facility.state_slug, + assigned_facility.state_region_id, + assigned_facility.organization_slug, + assigned_facility.organization_region_id, + + -- Registration facility and regions + p.registration_facility_id, + registration_facility.facility_size, + registration_facility.facility_type, + registration_facility.facility_region_slug, + registration_facility.facility_region_id, + registration_facility.block_slug, + registration_facility.block_region_id, + registration_facility.district_slug, + registration_facility.district_region_id, + registration_facility.state_slug, + registration_facility.state_region_id, + registration_facility.organization_slug, + registration_facility.organization_region_id, + + -- Visit details + bps.blood_pressure_id, + bps.blood_pressure_facility_id AS bp_facility_id, + bps.blood_pressure_recorded_at AS bp_recorded_at, + bps.systolic, + bps.diastolic, + + bss.blood_sugar_id, + bss.blood_sugar_facility_id AS bs_facility_id, + bss.blood_sugar_recorded_at AS bs_recorded_at, + bss.blood_sugar_type, + bss.blood_sugar_value, + bss.blood_sugar_risk_state, + + visits.encounter_id, + visits.encounter_recorded_at, + visits.prescription_drug_id, + visits.prescription_drug_recorded_at, + visits.appointment_id, + visits.appointment_recorded_at, + visits.visited_facility_ids, + + -- Relative time calculations + (cal.year - DATE_PART('year', p.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) * 12 + + (cal.month - DATE_PART('month', p.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) + AS months_since_registration, + + (cal.year - DATE_PART('year', p.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) * 4 + + (cal.quarter - DATE_PART('quarter', p.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) + AS quarters_since_registration, + + visits.months_since_visit, + visits.quarters_since_visit, + bps.months_since_bp, + bps.quarters_since_bp, + bss.months_since_bs, + bss.quarters_since_bs, + + -- BP and treatment indicators + CASE + WHEN bps.systolic IS NULL OR bps.diastolic IS NULL THEN 'unknown' + WHEN bps.systolic < 140 AND bps.diastolic < 90 THEN 'controlled' + ELSE 'uncontrolled' + END AS last_bp_state, + + CASE + WHEN p.status = 'dead' THEN 'dead' + WHEN ( + (cal.year - DATE_PART('year', p.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) * 12 + + (cal.month - DATE_PART('month', p.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) < 12 + OR visits.months_since_visit < 12 + ) THEN 'under_care' + ELSE 'lost_to_follow_up' + END AS htn_care_state, + + CASE + WHEN visits.months_since_visit >= 3 OR visits.months_since_visit IS NULL THEN 'missed_visit' + WHEN bps.months_since_bp >= 3 OR bps.months_since_bp IS NULL THEN 'visited_no_bp' + WHEN bps.systolic < 140 AND bps.diastolic < 90 THEN 'controlled' + ELSE 'uncontrolled' + END AS htn_treatment_outcome_in_last_3_months, + + CASE + WHEN visits.months_since_visit >= 2 OR visits.months_since_visit IS NULL THEN 'missed_visit' + WHEN bps.months_since_bp >= 2 OR bps.months_since_bp IS NULL THEN 'visited_no_bp' + WHEN bps.systolic < 140 AND bps.diastolic < 90 THEN 'controlled' + ELSE 'uncontrolled' + END AS htn_treatment_outcome_in_last_2_months, + + CASE + WHEN visits.quarters_since_visit >= 1 OR visits.quarters_since_visit IS NULL THEN 'missed_visit' + WHEN bps.quarters_since_bp >= 1 OR bps.quarters_since_bp IS NULL THEN 'visited_no_bp' + WHEN bps.systolic < 140 AND bps.diastolic < 90 THEN 'controlled' + ELSE 'uncontrolled' + END AS htn_treatment_outcome_in_quarter, + + CASE + WHEN (visits.months_since_visit >= 3 OR visits.months_since_visit is NULL) THEN 'missed_visit' + WHEN (bss.months_since_bs >= 3 OR bss.months_since_bs is NULL) THEN 'visited_no_bs' + ELSE bss.blood_sugar_risk_state + END AS diabetes_treatment_outcome_in_last_3_months, + + CASE + WHEN visits.months_since_visit >= 2 OR visits.months_since_visit IS NULL THEN 'missed_visit' + WHEN bss.months_since_bs >= 2 OR bss.months_since_bs IS NULL THEN 'visited_no_bs' + ELSE bss.blood_sugar_risk_state + END AS diabetes_treatment_outcome_in_last_2_months, + + CASE + WHEN visits.quarters_since_visit >= 1 OR visits.quarters_since_visit IS NULL THEN 'missed_visit' + WHEN bss.quarters_since_bs >= 1 OR bss.quarters_since_bs IS NULL THEN 'visited_no_bs' + ELSE bss.blood_sugar_risk_state + END AS diabetes_treatment_outcome_in_quarter, + + ( + current_meds.amlodipine > past_meds.amlodipine OR + current_meds.telmisartan > past_meds.telmisartan OR + current_meds.losartan > past_meds.losartan OR + current_meds.atenolol > past_meds.atenolol OR + current_meds.enalapril > past_meds.enalapril OR + current_meds.chlorthalidone > past_meds.chlorthalidone OR + current_meds.hydrochlorothiazide > past_meds.hydrochlorothiazide + ) AS titrated + + FROM public.patients p + JOIN public.reporting_months cal + ON cal.month_date = $1 + AND p.recorded_at <= cal.month_date + INTERVAL '1 month' + INTERVAL '1 day' + AND (( + to_char(timezone((SELECT current_setting('TIMEZONE'::text) AS current_setting), TIMEZONE('UTC'::text, p.recorded_at)), 'YYYY-MM'::text) <= + to_char((cal.month_date)::timestamp with time zone, 'YYYY-MM'::text)) + ) + + LEFT OUTER JOIN public.reporting_patient_blood_pressures bps + ON p.id = bps.patient_id AND cal.month_date = bps.month_date + + LEFT OUTER JOIN public.reporting_patient_blood_sugars bss + ON p.id = bss.patient_id AND cal.month_date = bss.month_date + + LEFT OUTER JOIN public.reporting_patient_visits visits + ON p.id = visits.patient_id AND cal.month_date = visits.month_date + + LEFT OUTER JOIN public.medical_histories mh + ON p.id = mh.patient_id AND mh.deleted_at IS NULL + + LEFT OUTER JOIN public.reporting_prescriptions current_meds + ON current_meds.patient_id = p.id AND cal.month_date = current_meds.month_date + + LEFT OUTER JOIN public.reporting_prescriptions past_meds + ON past_meds.patient_id = p.id AND cal.month_date = past_meds.month_date + INTERVAL '1 month' + + INNER JOIN public.reporting_facilities registration_facility + ON registration_facility.facility_id = p.registration_facility_id + + INNER JOIN public.reporting_facilities assigned_facility + ON assigned_facility.facility_id = p.assigned_facility_id + + WHERE p.deleted_at IS NULL; + END; + $_$; + SQL + end +end diff --git a/db/migrate/20251119133109_recreate_view_reporting_patient_states.rb b/db/migrate/20251119133109_recreate_view_reporting_patient_states.rb new file mode 100644 index 0000000000..b54f4bbdae --- /dev/null +++ b/db/migrate/20251119133109_recreate_view_reporting_patient_states.rb @@ -0,0 +1,7 @@ +class RecreateViewReportingPatientStates < ActiveRecord::Migration[6.1] + def change + execute <<~SQL + CREATE OR REPLACE VIEW public.reporting_patient_states AS SELECT * FROM simple_reporting.reporting_patient_states; + SQL + end +end diff --git a/db/migrate/20251120141950_update_latest_blood_pressures_per_patient_per_months_to_version7.rb b/db/migrate/20251120141950_update_latest_blood_pressures_per_patient_per_months_to_version7.rb new file mode 100644 index 0000000000..7621805526 --- /dev/null +++ b/db/migrate/20251120141950_update_latest_blood_pressures_per_patient_per_months_to_version7.rb @@ -0,0 +1,122 @@ +class UpdateLatestBloodPressuresPerPatientPerMonthsToVersion7 < ActiveRecord::Migration[6.1] + def up + drop_view :latest_blood_pressures_per_patient_per_quarters, materialized: true + drop_view :latest_blood_pressures_per_patients, materialized: true + drop_view :latest_blood_pressures_per_patient_per_months, materialized: true + + execute <<~SQL + CREATE MATERIALIZED VIEW public.latest_blood_pressures_per_patient_per_months AS + WITH registered_patients AS ( + SELECT DISTINCT id, + registration_facility_id, + assigned_facility_id, + status, + recorded_at + FROM public.patients + WHERE deleted_at IS NULL + AND diagnosed_confirmed_at IS NOT NULL + ) + SELECT DISTINCT ON (blood_pressures.patient_id, (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at)))::text), (date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at)))::text)) + blood_pressures.id AS bp_id, + blood_pressures.patient_id AS patient_id, + registered_patients.registration_facility_id AS registration_facility_id, + registered_patients.assigned_facility_id AS assigned_facility_id, + registered_patients.status as patient_status, + blood_pressures.facility_id AS bp_facility_id, + timezone('UTC'::text, timezone('UTC'::text, blood_pressures.recorded_at)) AS bp_recorded_at, + timezone('UTC'::text, timezone('UTC'::text, registered_patients.recorded_at)) AS patient_recorded_at, + blood_pressures.systolic AS systolic, + blood_pressures.diastolic AS diastolic, + timezone('UTC'::text, timezone('UTC'::text, blood_pressures.deleted_at)) AS deleted_at, + medical_histories.hypertension as medical_history_hypertension, + date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at)))::text AS month, + date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at)))::text AS quarter, + date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at)))::text AS year + FROM public.blood_pressures JOIN registered_patients ON registered_patients.id = blood_pressures.patient_id + LEFT JOIN public.medical_histories ON medical_histories.patient_id = blood_pressures.patient_id + WHERE blood_pressures.deleted_at IS NULL + ORDER BY patient_id, year, month, blood_pressures.recorded_at DESC, bp_id + WITH NO DATA; + SQL + + add_index "latest_blood_pressures_per_patient_per_months", ["assigned_facility_id"], name: "index_bp_months_assigned_facility_id" + add_index "latest_blood_pressures_per_patient_per_months", ["bp_recorded_at"], name: "index_bp_months_bp_recorded_at" + add_index "latest_blood_pressures_per_patient_per_months", ["patient_recorded_at"], name: "index_bp_months_patient_recorded_at" + add_index "latest_blood_pressures_per_patient_per_months", ["bp_id"], name: "index_latest_blood_pressures_per_patient_per_months", unique: true + add_index "latest_blood_pressures_per_patient_per_months", ["patient_id"], name: "index_latest_bp_per_patient_per_months_patient_id" + + execute <<~SQL + CREATE MATERIALIZED VIEW public.latest_blood_pressures_per_patient_per_quarters AS + SELECT DISTINCT ON (patient_id, year, quarter) * + FROM latest_blood_pressures_per_patient_per_months + ORDER BY patient_id, year, quarter, bp_recorded_at DESC, bp_id + WITH NO DATA; + SQL + add_index "latest_blood_pressures_per_patient_per_quarters", ["bp_id"], name: "index_latest_blood_pressures_per_patient_per_quarters", unique: true + add_index "latest_blood_pressures_per_patient_per_quarters", ["patient_id"], name: "index_latest_bp_per_patient_per_quarters_patient_id" + + execute <<~SQL + CREATE MATERIALIZED VIEW public.latest_blood_pressures_per_patients AS + SELECT DISTINCT ON (patient_id) * + FROM latest_blood_pressures_per_patient_per_months + ORDER BY patient_id, bp_recorded_at DESC, bp_id + WITH NO DATA; + SQL + add_index "latest_blood_pressures_per_patients", ["bp_id"], name: "index_latest_blood_pressures_per_patients", unique: true + end + + def down + drop_view :latest_blood_pressures_per_patient_per_quarters, materialized: true + drop_view :latest_blood_pressures_per_patients, materialized: true + drop_view :latest_blood_pressures_per_patient_per_months, materialized: true + + execute <<~SQL + CREATE MATERIALIZED VIEW public.latest_blood_pressures_per_patient_per_months AS + SELECT DISTINCT ON (patient_id, year, month) + blood_pressures.id AS bp_id, + blood_pressures.patient_id AS patient_id, + patients.registration_facility_id AS registration_facility_id, + patients.assigned_facility_id AS assigned_facility_id, + patients.status as patient_status, + blood_pressures.facility_id AS bp_facility_id, + blood_pressures.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE 'UTC' AS bp_recorded_at, + patients.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE 'UTC' AS patient_recorded_at, + blood_pressures.systolic AS systolic, + blood_pressures.diastolic AS diastolic, + blood_pressures.deleted_at AT TIME ZONE 'UTC' AT TIME ZONE 'UTC' AS deleted_at, + medical_histories.hypertension as medical_history_hypertension, + cast(EXTRACT(MONTH FROM blood_pressures.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE'))) as text) AS month, + cast(EXTRACT(QUARTER FROM blood_pressures.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE'))) as text) AS quarter, + cast(EXTRACT(YEAR FROM blood_pressures.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE'))) as text) AS year + FROM blood_pressures JOIN patients ON patients.id = blood_pressures.patient_id + LEFT JOIN medical_histories ON medical_histories.patient_id = blood_pressures.patient_id + WHERE blood_pressures.deleted_at IS NULL AND patients.deleted_at IS NULL + ORDER BY patient_id, year, month, blood_pressures.recorded_at DESC, bp_id + WITH NO DATA; + SQL + add_index "latest_blood_pressures_per_patient_per_months", ["assigned_facility_id"], name: "index_bp_months_assigned_facility_id" + add_index "latest_blood_pressures_per_patient_per_months", ["bp_recorded_at"], name: "index_bp_months_bp_recorded_at" + add_index "latest_blood_pressures_per_patient_per_months", ["patient_recorded_at"], name: "index_bp_months_patient_recorded_at" + add_index "latest_blood_pressures_per_patient_per_months", ["bp_id"], name: "index_latest_blood_pressures_per_patient_per_months", unique: true + add_index "latest_blood_pressures_per_patient_per_months", ["patient_id"], name: "index_latest_bp_per_patient_per_months_patient_id" + + execute <<~SQL + CREATE MATERIALIZED VIEW public.latest_blood_pressures_per_patient_per_quarters AS + SELECT DISTINCT ON (patient_id, year, quarter) * + FROM latest_blood_pressures_per_patient_per_months + ORDER BY patient_id, year, quarter, bp_recorded_at DESC, bp_id + WITH NO DATA; + SQL + add_index "latest_blood_pressures_per_patient_per_quarters", ["bp_id"], name: "index_latest_blood_pressures_per_patient_per_quarters", unique: true + add_index "latest_blood_pressures_per_patient_per_quarters", ["patient_id"], name: "index_latest_bp_per_patient_per_quarters_patient_id" + + execute <<~SQL + CREATE MATERIALIZED VIEW public.latest_blood_pressures_per_patients AS + SELECT DISTINCT ON (patient_id) * + FROM latest_blood_pressures_per_patient_per_months + ORDER BY patient_id, bp_recorded_at DESC, bp_id + WITH NO DATA; + SQL + add_index "latest_blood_pressures_per_patients", ["bp_id"], name: "index_latest_blood_pressures_per_patients", unique: true + end +end diff --git a/db/migrate/20251126052630_update_blood_pressures_per_facility_per_days_to_version3.rb b/db/migrate/20251126052630_update_blood_pressures_per_facility_per_days_to_version3.rb new file mode 100644 index 0000000000..f2698f3f74 --- /dev/null +++ b/db/migrate/20251126052630_update_blood_pressures_per_facility_per_days_to_version3.rb @@ -0,0 +1,73 @@ +class UpdateBloodPressuresPerFacilityPerDaysToVersion3 < ActiveRecord::Migration[6.1] + def up + drop_view :blood_pressures_per_facility_per_days, materialized: true + execute <<~SQL + CREATE MATERIALIZED VIEW public.blood_pressures_per_facility_per_days AS + WITH registered_patients AS ( + SELECT DISTINCT id + FROM public.patients + WHERE deleted_at IS NULL + AND diagnosed_confirmed_at IS NOT NULL + ), + latest_bp_per_patient_per_day AS ( + SELECT DISTINCT ON (blood_pressures.facility_id, blood_pressures.patient_id, (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text, (date_part('doy'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text) blood_pressures.id AS bp_id, + blood_pressures.facility_id, + (date_part('doy'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS day, + (date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS month, + (date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS quarter, + (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS year + FROM (public.blood_pressures + JOIN public.medical_histories ON (((blood_pressures.patient_id = medical_histories.patient_id) AND (medical_histories.hypertension = 'yes'::text))) + JOIN registered_patients ON registered_patients.id = blood_pressures.patient_id + ) + WHERE (blood_pressures.deleted_at IS NULL) + ORDER BY blood_pressures.facility_id, blood_pressures.patient_id, (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text, (date_part('doy'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text, blood_pressures.recorded_at DESC, blood_pressures.id + ) + SELECT count(latest_bp_per_patient_per_day.bp_id) AS bp_count, + facilities.id AS facility_id, + timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, facilities.deleted_at)) AS deleted_at, + latest_bp_per_patient_per_day.day, + latest_bp_per_patient_per_day.month, + latest_bp_per_patient_per_day.quarter, + latest_bp_per_patient_per_day.year + FROM (latest_bp_per_patient_per_day + JOIN public.facilities ON ((facilities.id = latest_bp_per_patient_per_day.facility_id))) + GROUP BY latest_bp_per_patient_per_day.day, latest_bp_per_patient_per_day.month, latest_bp_per_patient_per_day.quarter, latest_bp_per_patient_per_day.year, facilities.deleted_at, facilities.id + WITH NO DATA; + SQL + + add_index "blood_pressures_per_facility_per_days", ["facility_id", "day", "year"], name: "index_blood_pressures_per_facility_per_days", unique: true + end + + def down + drop_view :blood_pressures_per_facility_per_days, materialized: true + execute <<~SQL + CREATE MATERIALIZED VIEW public.blood_pressures_per_facility_per_days AS + WITH latest_bp_per_patient_per_day AS ( + SELECT DISTINCT ON (blood_pressures.facility_id, blood_pressures.patient_id, (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text, (date_part('doy'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text) blood_pressures.id AS bp_id, + blood_pressures.facility_id, + (date_part('doy'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS day, + (date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS month, + (date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS quarter, + (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS year + FROM (public.blood_pressures + JOIN public.medical_histories ON (((blood_pressures.patient_id = medical_histories.patient_id) AND (medical_histories.hypertension = 'yes'::text)))) + WHERE (blood_pressures.deleted_at IS NULL) + ORDER BY blood_pressures.facility_id, blood_pressures.patient_id, (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text, (date_part('doy'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text, blood_pressures.recorded_at DESC, blood_pressures.id + ) + SELECT count(latest_bp_per_patient_per_day.bp_id) AS bp_count, + facilities.id AS facility_id, + timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, facilities.deleted_at)) AS deleted_at, + latest_bp_per_patient_per_day.day, + latest_bp_per_patient_per_day.month, + latest_bp_per_patient_per_day.quarter, + latest_bp_per_patient_per_day.year + FROM (latest_bp_per_patient_per_day + JOIN public.facilities ON ((facilities.id = latest_bp_per_patient_per_day.facility_id))) + GROUP BY latest_bp_per_patient_per_day.day, latest_bp_per_patient_per_day.month, latest_bp_per_patient_per_day.quarter, latest_bp_per_patient_per_day.year, facilities.deleted_at, facilities.id + WITH NO DATA; + SQL + + add_index "blood_pressures_per_facility_per_days", ["facility_id", "day", "year"], name: "index_blood_pressures_per_facility_per_days", unique: true + end +end diff --git a/db/migrate/20251127104720_update_reporting_patient_blood_pressures_to_version4.rb b/db/migrate/20251127104720_update_reporting_patient_blood_pressures_to_version4.rb new file mode 100644 index 0000000000..16ceeef124 --- /dev/null +++ b/db/migrate/20251127104720_update_reporting_patient_blood_pressures_to_version4.rb @@ -0,0 +1,70 @@ +class UpdateReportingPatientBloodPressuresToVersion4 < ActiveRecord::Migration[6.1] + def up + drop_view :reporting_patient_blood_pressures, materialized: true + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_patient_blood_pressures AS + SELECT DISTINCT ON (bp.patient_id, cal.month_date) cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string, + timezone('UTC'::text, timezone('UTC'::text, bp.recorded_at)) AS blood_pressure_recorded_at, + bp.id AS blood_pressure_id, + bp.patient_id, + bp.systolic, + bp.diastolic, + bp.facility_id AS blood_pressure_facility_id, + timezone('UTC'::text, timezone('UTC'::text, p.diagnosed_confirmed_at)) AS patient_registered_at, + p.assigned_facility_id AS patient_assigned_facility_id, + p.registration_facility_id AS patient_registration_facility_id, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at)))) * (12)::double precision) + (cal.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))) AS months_since_registration, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at)))) * (4)::double precision) + (cal.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))) AS quarters_since_registration, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)))) * (12)::double precision) + (cal.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at))))) AS months_since_bp, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)))) * (4)::double precision) + (cal.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at))))) AS quarters_since_bp + FROM ((public.blood_pressures bp + LEFT JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)), 'YYYY-MM'::text) <= to_char((cal.month_date)::timestamp with time zone, 'YYYY-MM'::text)))) + JOIN public.patients p ON (((bp.patient_id = p.id) AND (p.deleted_at IS NULL) AND (p.diagnosed_confirmed_at IS NOT NULL)))) + WHERE (bp.deleted_at IS NULL) + ORDER BY bp.patient_id, cal.month_date, bp.recorded_at DESC + WITH NO DATA; + SQL + + add_index "reporting_patient_blood_pressures", ["month_date", "patient_id"], name: "patient_blood_pressures_patient_id_month_date", unique: true + end + + def down + drop_view :reporting_patient_blood_pressures, materialized: true + + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_patient_blood_pressures AS + SELECT DISTINCT ON (bp.patient_id, cal.month_date) cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string, + timezone('UTC'::text, timezone('UTC'::text, bp.recorded_at)) AS blood_pressure_recorded_at, + bp.id AS blood_pressure_id, + bp.patient_id, + bp.systolic, + bp.diastolic, + bp.facility_id AS blood_pressure_facility_id, + timezone('UTC'::text, timezone('UTC'::text, p.recorded_at)) AS patient_registered_at, + p.assigned_facility_id AS patient_assigned_facility_id, + p.registration_facility_id AS patient_registration_facility_id, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at)))) * (12)::double precision) + (cal.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at))))) AS months_since_registration, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at)))) * (4)::double precision) + (cal.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at))))) AS quarters_since_registration, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)))) * (12)::double precision) + (cal.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at))))) AS months_since_bp, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)))) * (4)::double precision) + (cal.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at))))) AS quarters_since_bp + FROM ((public.blood_pressures bp + LEFT JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)), 'YYYY-MM'::text) <= to_char((cal.month_date)::timestamp with time zone, 'YYYY-MM'::text)))) + JOIN public.patients p ON (((bp.patient_id = p.id) AND (p.deleted_at IS NULL)))) + WHERE (bp.deleted_at IS NULL) + ORDER BY bp.patient_id, cal.month_date, bp.recorded_at DESC + WITH NO DATA; + SQL + + add_index "reporting_patient_blood_pressures", ["month_date", "patient_id"], name: "patient_blood_pressures_patient_id_month_date", unique: true + end +end diff --git a/db/migrate/20251201094315_update_reporting_patient_blood_sugars_to_version2.rb b/db/migrate/20251201094315_update_reporting_patient_blood_sugars_to_version2.rb new file mode 100644 index 0000000000..bf04174dae --- /dev/null +++ b/db/migrate/20251201094315_update_reporting_patient_blood_sugars_to_version2.rb @@ -0,0 +1,117 @@ +class UpdateReportingPatientBloodSugarsToVersion2 < ActiveRecord::Migration[6.1] + def up + drop_view :reporting_patient_blood_sugars, materialized: true + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_patient_blood_sugars AS + SELECT DISTINCT ON (bs.patient_id, cal.month_date) cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string, + timezone('UTC'::text, timezone('UTC'::text, bs.recorded_at)) AS blood_sugar_recorded_at, + bs.id AS blood_sugar_id, + bs.patient_id, + bs.blood_sugar_type, + bs.blood_sugar_value, + bs.facility_id AS blood_sugar_facility_id, + CASE + WHEN (((bs.blood_sugar_type)::text = 'random'::text) OR ((bs.blood_sugar_type)::text = 'post_prandial'::text)) THEN + CASE + WHEN (bs.blood_sugar_value < 200.0) THEN 'bs_below_200'::text + WHEN ((bs.blood_sugar_value >= 200.0) AND (bs.blood_sugar_value < 300.0)) THEN 'bs_200_to_300'::text + WHEN (bs.blood_sugar_value >= 300.0) THEN 'bs_over_300'::text + ELSE NULL::text + END + WHEN ((bs.blood_sugar_type)::text = 'fasting'::text) THEN + CASE + WHEN (bs.blood_sugar_value < 126.0) THEN 'bs_below_200'::text + WHEN ((bs.blood_sugar_value >= 126.0) AND (bs.blood_sugar_value < 200.0)) THEN 'bs_200_to_300'::text + WHEN (bs.blood_sugar_value >= 200.0) THEN 'bs_over_300'::text + ELSE NULL::text + END + WHEN ((bs.blood_sugar_type)::text = 'hba1c'::text) THEN + CASE + WHEN (bs.blood_sugar_value < 7.0) THEN 'bs_below_200'::text + WHEN ((bs.blood_sugar_value >= 7.0) AND (bs.blood_sugar_value < 9.0)) THEN 'bs_200_to_300'::text + WHEN (bs.blood_sugar_value >= 9.0) THEN 'bs_over_300'::text + ELSE NULL::text + END + ELSE NULL::text + END AS blood_sugar_risk_state, + timezone('UTC'::text, timezone('UTC'::text, p.diagnosed_confirmed_at)) AS patient_registered_at, + p.assigned_facility_id AS patient_assigned_facility_id, + p.registration_facility_id AS patient_registration_facility_id, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at)))) * (12)::double precision) + (cal.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))) AS months_since_registration, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at)))) * (4)::double precision) + (cal.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))) AS quarters_since_registration, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)))) * (12)::double precision) + (cal.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at))))) AS months_since_bs, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)))) * (4)::double precision) + (cal.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at))))) AS quarters_since_bs + FROM ((public.blood_sugars bs + LEFT JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)), 'YYYY-MM'::text) <= to_char((cal.month_date)::timestamp with time zone, 'YYYY-MM'::text)))) + JOIN public.patients p ON (((bs.patient_id = p.id) AND (p.deleted_at IS NULL) AND (p.diagnosed_confirmed_at IS NOT NULL)))) + WHERE (bs.deleted_at IS NULL) + ORDER BY bs.patient_id, cal.month_date, bs.recorded_at DESC + WITH NO DATA; + SQL + add_index "reporting_patient_blood_sugars", ["month_date", "patient_id"], name: "patient_blood_sugars_month_date_patient_id", unique: true + end + + def down + drop_view :reporting_patient_blood_sugars, materialized: true + + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_patient_blood_sugars AS + SELECT DISTINCT ON (bs.patient_id, cal.month_date) cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string, + timezone('UTC'::text, timezone('UTC'::text, bs.recorded_at)) AS blood_sugar_recorded_at, + bs.id AS blood_sugar_id, + bs.patient_id, + bs.blood_sugar_type, + bs.blood_sugar_value, + bs.facility_id AS blood_sugar_facility_id, + CASE + WHEN (((bs.blood_sugar_type)::text = 'random'::text) OR ((bs.blood_sugar_type)::text = 'post_prandial'::text)) THEN + CASE + WHEN (bs.blood_sugar_value < 200.0) THEN 'bs_below_200'::text + WHEN ((bs.blood_sugar_value >= 200.0) AND (bs.blood_sugar_value < 300.0)) THEN 'bs_200_to_300'::text + WHEN (bs.blood_sugar_value >= 300.0) THEN 'bs_over_300'::text + ELSE NULL::text + END + WHEN ((bs.blood_sugar_type)::text = 'fasting'::text) THEN + CASE + WHEN (bs.blood_sugar_value < 126.0) THEN 'bs_below_200'::text + WHEN ((bs.blood_sugar_value >= 126.0) AND (bs.blood_sugar_value < 200.0)) THEN 'bs_200_to_300'::text + WHEN (bs.blood_sugar_value >= 200.0) THEN 'bs_over_300'::text + ELSE NULL::text + END + WHEN ((bs.blood_sugar_type)::text = 'hba1c'::text) THEN + CASE + WHEN (bs.blood_sugar_value < 7.0) THEN 'bs_below_200'::text + WHEN ((bs.blood_sugar_value >= 7.0) AND (bs.blood_sugar_value < 9.0)) THEN 'bs_200_to_300'::text + WHEN (bs.blood_sugar_value >= 9.0) THEN 'bs_over_300'::text + ELSE NULL::text + END + ELSE NULL::text + END AS blood_sugar_risk_state, + timezone('UTC'::text, timezone('UTC'::text, p.recorded_at)) AS patient_registered_at, + p.assigned_facility_id AS patient_assigned_facility_id, + p.registration_facility_id AS patient_registration_facility_id, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at)))) * (12)::double precision) + (cal.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at))))) AS months_since_registration, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at)))) * (4)::double precision) + (cal.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at))))) AS quarters_since_registration, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)))) * (12)::double precision) + (cal.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at))))) AS months_since_bs, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)))) * (4)::double precision) + (cal.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at))))) AS quarters_since_bs + FROM ((public.blood_sugars bs + LEFT JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)), 'YYYY-MM'::text) <= to_char((cal.month_date)::timestamp with time zone, 'YYYY-MM'::text)))) + JOIN public.patients p ON (((bs.patient_id = p.id) AND (p.deleted_at IS NULL)))) + WHERE (bs.deleted_at IS NULL) + ORDER BY bs.patient_id, cal.month_date, bs.recorded_at DESC + WITH NO DATA; + SQL + + add_index "reporting_patient_blood_sugars", ["month_date", "patient_id"], name: "patient_blood_sugars_month_date_patient_id", unique: true + end +end diff --git a/db/migrate/20251202062322_update_reporting_overdue_calls_to_version2.rb b/db/migrate/20251202062322_update_reporting_overdue_calls_to_version2.rb new file mode 100644 index 0000000000..45d8bf2130 --- /dev/null +++ b/db/migrate/20251202062322_update_reporting_overdue_calls_to_version2.rb @@ -0,0 +1,630 @@ +class UpdateReportingOverdueCallsToVersion2 < ActiveRecord::Migration[6.1] + def up + drop_view :reporting_facility_states, materialized: true + drop_view :reporting_overdue_calls, materialized: true + + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_overdue_calls AS + SELECT DISTINCT ON (a.patient_id, cal.month_date) cal.month_date, + cal.month_string, + cal.month, + cal.quarter, + cal.year, + timezone('UTC'::text, timezone('UTC'::text, cr.device_created_at)) AS call_result_created_at, + cr.id AS call_result_id, + cr.user_id, + a.id AS appointment_id, + a.facility_id AS appointment_facility_id, + a.patient_id, + appointment_facility.facility_size AS appointment_facility_size, + appointment_facility.facility_type AS appointment_facility_type, + appointment_facility.facility_region_slug AS appointment_facility_slug, + appointment_facility.facility_region_id AS appointment_facility_region_id, + appointment_facility.block_slug AS appointment_block_slug, + appointment_facility.block_region_id AS appointment_block_region_id, + appointment_facility.district_slug AS appointment_district_slug, + appointment_facility.district_region_id AS appointment_district_region_id, + appointment_facility.state_slug AS appointment_state_slug, + appointment_facility.state_region_id AS appointment_state_region_id, + appointment_facility.organization_slug AS appointment_organization_slug, + appointment_facility.organization_region_id AS appointment_organization_region_id + FROM (((public.call_results cr + JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, cr.device_created_at)), 'YYYY-MM'::text) = to_char((cal.month_date)::timestamp with time zone, 'YYYY-MM'::text)))) + JOIN public.appointments a ON (((cr.appointment_id = a.id) AND (a.deleted_at IS NULL)))) + JOIN public.patients p ON (p.id = a.patient_id AND ((p.deleted_at IS NULL) AND (p.diagnosed_confirmed_at IS NOT NULL))) + JOIN public.reporting_facilities appointment_facility ON ((a.facility_id = appointment_facility.facility_id))) + WHERE (cr.deleted_at IS NULL) + ORDER BY a.patient_id, cal.month_date, cr.device_created_at DESC + WITH NO DATA; + SQL + + add_index "reporting_overdue_calls", ["appointment_facility_region_id"], name: "index_overdue_calls_appointment_facility" + add_index "reporting_overdue_calls", ["call_result_created_at"], name: "index_overdue_calls_call_result_created_at" + add_index "reporting_overdue_calls", ["month_date", "patient_id"], name: "overdue_calls_month_date_patient_id", unique: true + + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_facility_states AS + WITH registered_patients AS ( + SELECT reporting_patient_states.registration_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(*) AS cumulative_registrations, + count(*) FILTER (WHERE (reporting_patient_states.months_since_registration = (0)::double precision)) AS monthly_registrations + FROM public.reporting_patient_states + WHERE (reporting_patient_states.hypertension = 'yes'::text) + GROUP BY reporting_patient_states.registration_facility_region_id, reporting_patient_states.month_date + ), registered_diabetes_patients AS ( + SELECT reporting_patient_states.registration_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(*) AS cumulative_diabetes_registrations, + count(*) FILTER (WHERE (reporting_patient_states.months_since_registration = (0)::double precision)) AS monthly_diabetes_registrations + FROM public.reporting_patient_states + WHERE (reporting_patient_states.diabetes = 'yes'::text) + GROUP BY reporting_patient_states.registration_facility_region_id, reporting_patient_states.month_date + ), registered_hypertension_and_diabetes_patients AS ( + SELECT reporting_patient_states.registration_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(*) AS cumulative_hypertension_and_diabetes_registrations, + count(*) FILTER (WHERE (reporting_patient_states.months_since_registration = (0)::double precision)) AS monthly_hypertension_and_diabetes_registrations + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.hypertension = 'yes'::text) AND (reporting_patient_states.diabetes = 'yes'::text)) + GROUP BY reporting_patient_states.registration_facility_region_id, reporting_patient_states.month_date + ), assigned_patients AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'dead'::text)) AS dead, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state <> 'dead'::text)) AS cumulative_assigned_patients + FROM public.reporting_patient_states + WHERE (reporting_patient_states.hypertension = 'yes'::text) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), assigned_diabetes_patients AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS diabetes_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS diabetes_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'dead'::text)) AS diabetes_dead, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state <> 'dead'::text)) AS cumulative_assigned_diabetic_patients + FROM public.reporting_patient_states + WHERE (reporting_patient_states.diabetes = 'yes'::text) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), adjusted_outcomes AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'controlled'::text))) AS controlled_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'uncontrolled'::text))) AS uncontrolled_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS missed_visit_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'visited_no_bp'::text))) AS visited_no_bp_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS missed_visit_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'visited_no_bp'::text))) AS visited_no_bp_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS patients_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS patients_lost_to_follow_up + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.hypertension = 'yes'::text) AND (reporting_patient_states.months_since_registration >= (3)::double precision)) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), adjusted_diabetes_outcomes AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'random'::text))) AS random_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'post_prandial'::text))) AS post_prandial_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'fasting'::text))) AS fasting_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'hba1c'::text))) AS hba1c_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text))) AS bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'random'::text))) AS random_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'post_prandial'::text))) AS post_prandial_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'fasting'::text))) AS fasting_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'hba1c'::text))) AS hba1c_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text))) AS bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'random'::text))) AS random_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'post_prandial'::text))) AS post_prandial_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'fasting'::text))) AS fasting_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'hba1c'::text))) AS hba1c_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text))) AS bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS bs_missed_visit_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'visited_no_bs'::text))) AS visited_no_bs_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS bs_missed_visit_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS diabetes_patients_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS diabetes_patients_lost_to_follow_up + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.diabetes = 'yes'::text) AND (reporting_patient_states.months_since_registration >= (3)::double precision)) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), monthly_cohort_outcomes AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'controlled'::text)) AS controlled, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'uncontrolled'::text)) AS uncontrolled, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'missed_visit'::text)) AS missed_visit, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'visited_no_bp'::text)) AS visited_no_bp, + count(DISTINCT reporting_patient_states.patient_id) AS patients + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.hypertension = 'yes'::text) AND (reporting_patient_states.months_since_registration = (2)::double precision)) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), monthly_overdue_calls AS ( + SELECT reporting_overdue_calls.appointment_facility_region_id AS region_id, + reporting_overdue_calls.month_date, + count(DISTINCT reporting_overdue_calls.appointment_id) AS call_results + FROM public.reporting_overdue_calls + GROUP BY reporting_overdue_calls.appointment_facility_region_id, reporting_overdue_calls.month_date + ), monthly_follow_ups AS ( + SELECT reporting_patient_follow_ups.facility_id, + reporting_patient_follow_ups.month_date, + count(DISTINCT reporting_patient_follow_ups.patient_id) AS follow_ups + FROM public.reporting_patient_follow_ups + WHERE (reporting_patient_follow_ups.hypertension = 'yes'::text) + GROUP BY reporting_patient_follow_ups.facility_id, reporting_patient_follow_ups.month_date + ), monthly_diabetes_follow_ups AS ( + SELECT reporting_patient_follow_ups.facility_id, + reporting_patient_follow_ups.month_date, + count(DISTINCT reporting_patient_follow_ups.patient_id) AS follow_ups + FROM public.reporting_patient_follow_ups + WHERE (reporting_patient_follow_ups.diabetes = 'yes'::text) + GROUP BY reporting_patient_follow_ups.facility_id, reporting_patient_follow_ups.month_date + ), monthly_hypertension_overdue_patients AS ( + SELECT reporting_overdue_patients.assigned_facility_region_id, + reporting_overdue_patients.month_date, + count(*) FILTER (WHERE (reporting_overdue_patients.is_overdue = 'yes'::text)) AS overdue_patients, + count(*) FILTER (WHERE ((reporting_overdue_patients.is_overdue = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text))) AS contactable_overdue_patients, + count(*) FILTER (WHERE (reporting_overdue_patients.has_called = 'yes'::text)) AS patients_called, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS patients_called_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS patients_called_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS patients_called_with_result_removed_from_list, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text))) AS contactable_patients_called, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS contactable_patients_called_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS contactable_patients_called_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS contactable_patients_called_with_result_removed_from_list, + count(*) FILTER (WHERE (reporting_overdue_patients.has_visited_following_call = 'yes'::text)) AS patients_returned_after_call, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS patients_returned_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS patients_returned_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS patients_returned_with_result_removed_from_list, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text))) AS contactable_patients_returned_after_call, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS contactable_patients_returned_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS contactable_patients_returned_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS contactable_patients_returned_with_result_removed_from_list + FROM public.reporting_overdue_patients + WHERE ((reporting_overdue_patients.hypertension = 'yes'::text) AND (reporting_overdue_patients.under_care = 'yes'::text)) + GROUP BY reporting_overdue_patients.assigned_facility_region_id, reporting_overdue_patients.month_date + ) + SELECT cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string, + rf.facility_id, + rf.facility_name, + rf.facility_type, + rf.facility_size, + rf.facility_region_id, + rf.facility_region_name, + rf.facility_region_slug, + rf.block_region_id, + rf.block_name, + rf.block_slug, + rf.district_id, + rf.district_region_id, + rf.district_name, + rf.district_slug, + rf.state_region_id, + rf.state_name, + rf.state_slug, + rf.organization_id, + rf.organization_region_id, + rf.organization_name, + rf.organization_slug, + registered_patients.cumulative_registrations, + registered_patients.monthly_registrations, + registered_diabetes_patients.cumulative_diabetes_registrations, + registered_diabetes_patients.monthly_diabetes_registrations, + registered_hypertension_and_diabetes_patients.cumulative_hypertension_and_diabetes_registrations, + registered_hypertension_and_diabetes_patients.monthly_hypertension_and_diabetes_registrations, + assigned_patients.under_care, + assigned_patients.lost_to_follow_up, + assigned_patients.dead, + assigned_patients.cumulative_assigned_patients, + assigned_diabetes_patients.diabetes_under_care, + assigned_diabetes_patients.diabetes_lost_to_follow_up, + assigned_diabetes_patients.diabetes_dead, + assigned_diabetes_patients.cumulative_assigned_diabetic_patients, + adjusted_outcomes.controlled_under_care AS adjusted_controlled_under_care, + adjusted_outcomes.uncontrolled_under_care AS adjusted_uncontrolled_under_care, + adjusted_outcomes.missed_visit_under_care AS adjusted_missed_visit_under_care, + adjusted_outcomes.visited_no_bp_under_care AS adjusted_visited_no_bp_under_care, + adjusted_outcomes.missed_visit_lost_to_follow_up AS adjusted_missed_visit_lost_to_follow_up, + adjusted_outcomes.visited_no_bp_lost_to_follow_up AS adjusted_visited_no_bp_lost_to_follow_up, + adjusted_outcomes.patients_under_care AS adjusted_patients_under_care, + adjusted_outcomes.patients_lost_to_follow_up AS adjusted_patients_lost_to_follow_up, + adjusted_diabetes_outcomes.random_bs_below_200_under_care AS adjusted_random_bs_below_200_under_care, + adjusted_diabetes_outcomes.fasting_bs_below_200_under_care AS adjusted_fasting_bs_below_200_under_care, + adjusted_diabetes_outcomes.post_prandial_bs_below_200_under_care AS adjusted_post_prandial_bs_below_200_under_care, + adjusted_diabetes_outcomes.hba1c_bs_below_200_under_care AS adjusted_hba1c_bs_below_200_under_care, + adjusted_diabetes_outcomes.bs_below_200_under_care AS adjusted_bs_below_200_under_care, + adjusted_diabetes_outcomes.random_bs_200_to_300_under_care AS adjusted_random_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.fasting_bs_200_to_300_under_care AS adjusted_fasting_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.post_prandial_bs_200_to_300_under_care AS adjusted_post_prandial_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.hba1c_bs_200_to_300_under_care AS adjusted_hba1c_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.bs_200_to_300_under_care AS adjusted_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.random_bs_over_300_under_care AS adjusted_random_bs_over_300_under_care, + adjusted_diabetes_outcomes.fasting_bs_over_300_under_care AS adjusted_fasting_bs_over_300_under_care, + adjusted_diabetes_outcomes.post_prandial_bs_over_300_under_care AS adjusted_post_prandial_bs_over_300_under_care, + adjusted_diabetes_outcomes.hba1c_bs_over_300_under_care AS adjusted_hba1c_bs_over_300_under_care, + adjusted_diabetes_outcomes.bs_over_300_under_care AS adjusted_bs_over_300_under_care, + adjusted_diabetes_outcomes.bs_missed_visit_under_care AS adjusted_bs_missed_visit_under_care, + adjusted_diabetes_outcomes.visited_no_bs_under_care AS adjusted_visited_no_bs_under_care, + adjusted_diabetes_outcomes.bs_missed_visit_lost_to_follow_up AS adjusted_bs_missed_visit_lost_to_follow_up, + adjusted_diabetes_outcomes.diabetes_patients_under_care AS adjusted_diabetes_patients_under_care, + adjusted_diabetes_outcomes.diabetes_patients_lost_to_follow_up AS adjusted_diabetes_patients_lost_to_follow_up, + monthly_cohort_outcomes.controlled AS monthly_cohort_controlled, + monthly_cohort_outcomes.uncontrolled AS monthly_cohort_uncontrolled, + monthly_cohort_outcomes.missed_visit AS monthly_cohort_missed_visit, + monthly_cohort_outcomes.visited_no_bp AS monthly_cohort_visited_no_bp, + monthly_cohort_outcomes.patients AS monthly_cohort_patients, + monthly_overdue_calls.call_results AS monthly_overdue_calls, + monthly_follow_ups.follow_ups AS monthly_follow_ups, + monthly_diabetes_follow_ups.follow_ups AS monthly_diabetes_follow_ups, + reporting_facility_appointment_scheduled_days.htn_total_appts_scheduled AS total_appts_scheduled, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_0_to_14_days AS appts_scheduled_0_to_14_days, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_15_to_31_days AS appts_scheduled_15_to_31_days, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_32_to_62_days AS appts_scheduled_32_to_62_days, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_more_than_62_days AS appts_scheduled_more_than_62_days, + reporting_facility_appointment_scheduled_days.diabetes_total_appts_scheduled, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_0_to_14_days, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_15_to_31_days, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_32_to_62_days, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_more_than_62_days, + monthly_hypertension_overdue_patients.overdue_patients, + monthly_hypertension_overdue_patients.contactable_overdue_patients, + monthly_hypertension_overdue_patients.patients_called, + monthly_hypertension_overdue_patients.contactable_patients_called, + monthly_hypertension_overdue_patients.patients_called_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.patients_called_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.patients_called_with_result_removed_from_list, + monthly_hypertension_overdue_patients.contactable_patients_called_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.contactable_patients_called_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.contactable_patients_called_with_result_removed_from_list, + monthly_hypertension_overdue_patients.patients_returned_after_call, + monthly_hypertension_overdue_patients.contactable_patients_returned_after_call, + monthly_hypertension_overdue_patients.patients_returned_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.patients_returned_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.patients_returned_with_result_removed_from_list, + monthly_hypertension_overdue_patients.contactable_patients_returned_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.contactable_patients_returned_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.contactable_patients_returned_with_result_removed_from_list + FROM ((((((((((((((public.reporting_facilities rf + JOIN public.reporting_months cal ON (true)) + LEFT JOIN registered_patients ON (((registered_patients.month_date = cal.month_date) AND (registered_patients.region_id = rf.facility_region_id)))) + LEFT JOIN registered_diabetes_patients ON (((registered_diabetes_patients.month_date = cal.month_date) AND (registered_diabetes_patients.region_id = rf.facility_region_id)))) + LEFT JOIN registered_hypertension_and_diabetes_patients ON (((registered_hypertension_and_diabetes_patients.month_date = cal.month_date) AND (registered_hypertension_and_diabetes_patients.region_id = rf.facility_region_id)))) + LEFT JOIN assigned_patients ON (((assigned_patients.month_date = cal.month_date) AND (assigned_patients.region_id = rf.facility_region_id)))) + LEFT JOIN assigned_diabetes_patients ON (((assigned_diabetes_patients.month_date = cal.month_date) AND (assigned_diabetes_patients.region_id = rf.facility_region_id)))) + LEFT JOIN adjusted_outcomes ON (((adjusted_outcomes.month_date = cal.month_date) AND (adjusted_outcomes.region_id = rf.facility_region_id)))) + LEFT JOIN adjusted_diabetes_outcomes ON (((adjusted_diabetes_outcomes.month_date = cal.month_date) AND (adjusted_diabetes_outcomes.region_id = rf.facility_region_id)))) + LEFT JOIN monthly_cohort_outcomes ON (((monthly_cohort_outcomes.month_date = cal.month_date) AND (monthly_cohort_outcomes.region_id = rf.facility_region_id)))) + LEFT JOIN monthly_overdue_calls ON (((monthly_overdue_calls.month_date = cal.month_date) AND (monthly_overdue_calls.region_id = rf.facility_region_id)))) + LEFT JOIN monthly_follow_ups ON (((monthly_follow_ups.month_date = cal.month_date) AND (monthly_follow_ups.facility_id = rf.facility_id)))) + LEFT JOIN monthly_diabetes_follow_ups ON (((monthly_diabetes_follow_ups.month_date = cal.month_date) AND (monthly_diabetes_follow_ups.facility_id = rf.facility_id)))) + LEFT JOIN public.reporting_facility_appointment_scheduled_days ON (((reporting_facility_appointment_scheduled_days.month_date = cal.month_date) AND (reporting_facility_appointment_scheduled_days.facility_id = rf.facility_id)))) + LEFT JOIN monthly_hypertension_overdue_patients ON (((monthly_hypertension_overdue_patients.month_date = cal.month_date) AND (monthly_hypertension_overdue_patients.assigned_facility_region_id = rf.facility_region_id)))) + WITH NO DATA; + SQL + + add_index "reporting_facility_states", ["block_region_id", "month_date"], name: "index_fs_block_month_date" + add_index "reporting_facility_states", ["district_region_id", "month_date"], name: "index_fs_district_month_date" + add_index "reporting_facility_states", ["month_date", "facility_region_id"], name: "index_fs_month_date_region_id", unique: true + add_index "reporting_facility_states", ["organization_region_id", "month_date"], name: "index_fs_organization_month_date" + add_index "reporting_facility_states", ["state_region_id", "month_date"], name: "index_fs_state_month_date" + end + + def down + drop_view :reporting_facility_states, materialized: true + drop_view :reporting_overdue_calls, materialized: true + + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_overdue_calls AS + SELECT DISTINCT ON (a.patient_id, cal.month_date) cal.month_date, + cal.month_string, + cal.month, + cal.quarter, + cal.year, + timezone('UTC'::text, timezone('UTC'::text, cr.device_created_at)) AS call_result_created_at, + cr.id AS call_result_id, + cr.user_id, + a.id AS appointment_id, + a.facility_id AS appointment_facility_id, + a.patient_id, + appointment_facility.facility_size AS appointment_facility_size, + appointment_facility.facility_type AS appointment_facility_type, + appointment_facility.facility_region_slug AS appointment_facility_slug, + appointment_facility.facility_region_id AS appointment_facility_region_id, + appointment_facility.block_slug AS appointment_block_slug, + appointment_facility.block_region_id AS appointment_block_region_id, + appointment_facility.district_slug AS appointment_district_slug, + appointment_facility.district_region_id AS appointment_district_region_id, + appointment_facility.state_slug AS appointment_state_slug, + appointment_facility.state_region_id AS appointment_state_region_id, + appointment_facility.organization_slug AS appointment_organization_slug, + appointment_facility.organization_region_id AS appointment_organization_region_id + FROM (((public.call_results cr + JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, cr.device_created_at)), 'YYYY-MM'::text) = to_char((cal.month_date)::timestamp with time zone, 'YYYY-MM'::text)))) + JOIN public.appointments a ON (((cr.appointment_id = a.id) AND (a.deleted_at IS NULL)))) + JOIN public.reporting_facilities appointment_facility ON ((a.facility_id = appointment_facility.facility_id))) + WHERE (cr.deleted_at IS NULL) + ORDER BY a.patient_id, cal.month_date, cr.device_created_at DESC + WITH NO DATA; + SQL + + add_index "reporting_overdue_calls", ["appointment_facility_region_id"], name: "index_overdue_calls_appointment_facility" + add_index "reporting_overdue_calls", ["call_result_created_at"], name: "index_overdue_calls_call_result_created_at" + add_index "reporting_overdue_calls", ["month_date", "patient_id"], name: "overdue_calls_month_date_patient_id", unique: true + + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_facility_states AS + WITH registered_patients AS ( + SELECT reporting_patient_states.registration_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(*) AS cumulative_registrations, + count(*) FILTER (WHERE (reporting_patient_states.months_since_registration = (0)::double precision)) AS monthly_registrations + FROM public.reporting_patient_states + WHERE (reporting_patient_states.hypertension = 'yes'::text) + GROUP BY reporting_patient_states.registration_facility_region_id, reporting_patient_states.month_date + ), registered_diabetes_patients AS ( + SELECT reporting_patient_states.registration_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(*) AS cumulative_diabetes_registrations, + count(*) FILTER (WHERE (reporting_patient_states.months_since_registration = (0)::double precision)) AS monthly_diabetes_registrations + FROM public.reporting_patient_states + WHERE (reporting_patient_states.diabetes = 'yes'::text) + GROUP BY reporting_patient_states.registration_facility_region_id, reporting_patient_states.month_date + ), registered_hypertension_and_diabetes_patients AS ( + SELECT reporting_patient_states.registration_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(*) AS cumulative_hypertension_and_diabetes_registrations, + count(*) FILTER (WHERE (reporting_patient_states.months_since_registration = (0)::double precision)) AS monthly_hypertension_and_diabetes_registrations + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.hypertension = 'yes'::text) AND (reporting_patient_states.diabetes = 'yes'::text)) + GROUP BY reporting_patient_states.registration_facility_region_id, reporting_patient_states.month_date + ), assigned_patients AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'dead'::text)) AS dead, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state <> 'dead'::text)) AS cumulative_assigned_patients + FROM public.reporting_patient_states + WHERE (reporting_patient_states.hypertension = 'yes'::text) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), assigned_diabetes_patients AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS diabetes_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS diabetes_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'dead'::text)) AS diabetes_dead, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state <> 'dead'::text)) AS cumulative_assigned_diabetic_patients + FROM public.reporting_patient_states + WHERE (reporting_patient_states.diabetes = 'yes'::text) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), adjusted_outcomes AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'controlled'::text))) AS controlled_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'uncontrolled'::text))) AS uncontrolled_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS missed_visit_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'visited_no_bp'::text))) AS visited_no_bp_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS missed_visit_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'visited_no_bp'::text))) AS visited_no_bp_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS patients_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS patients_lost_to_follow_up + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.hypertension = 'yes'::text) AND (reporting_patient_states.months_since_registration >= (3)::double precision)) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), adjusted_diabetes_outcomes AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'random'::text))) AS random_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'post_prandial'::text))) AS post_prandial_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'fasting'::text))) AS fasting_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'hba1c'::text))) AS hba1c_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text))) AS bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'random'::text))) AS random_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'post_prandial'::text))) AS post_prandial_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'fasting'::text))) AS fasting_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'hba1c'::text))) AS hba1c_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text))) AS bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'random'::text))) AS random_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'post_prandial'::text))) AS post_prandial_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'fasting'::text))) AS fasting_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'hba1c'::text))) AS hba1c_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text))) AS bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS bs_missed_visit_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'visited_no_bs'::text))) AS visited_no_bs_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS bs_missed_visit_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS diabetes_patients_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS diabetes_patients_lost_to_follow_up + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.diabetes = 'yes'::text) AND (reporting_patient_states.months_since_registration >= (3)::double precision)) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), monthly_cohort_outcomes AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'controlled'::text)) AS controlled, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'uncontrolled'::text)) AS uncontrolled, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'missed_visit'::text)) AS missed_visit, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'visited_no_bp'::text)) AS visited_no_bp, + count(DISTINCT reporting_patient_states.patient_id) AS patients + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.hypertension = 'yes'::text) AND (reporting_patient_states.months_since_registration = (2)::double precision)) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), monthly_overdue_calls AS ( + SELECT reporting_overdue_calls.appointment_facility_region_id AS region_id, + reporting_overdue_calls.month_date, + count(DISTINCT reporting_overdue_calls.appointment_id) AS call_results + FROM public.reporting_overdue_calls + GROUP BY reporting_overdue_calls.appointment_facility_region_id, reporting_overdue_calls.month_date + ), monthly_follow_ups AS ( + SELECT reporting_patient_follow_ups.facility_id, + reporting_patient_follow_ups.month_date, + count(DISTINCT reporting_patient_follow_ups.patient_id) AS follow_ups + FROM public.reporting_patient_follow_ups + WHERE (reporting_patient_follow_ups.hypertension = 'yes'::text) + GROUP BY reporting_patient_follow_ups.facility_id, reporting_patient_follow_ups.month_date + ), monthly_diabetes_follow_ups AS ( + SELECT reporting_patient_follow_ups.facility_id, + reporting_patient_follow_ups.month_date, + count(DISTINCT reporting_patient_follow_ups.patient_id) AS follow_ups + FROM public.reporting_patient_follow_ups + WHERE (reporting_patient_follow_ups.diabetes = 'yes'::text) + GROUP BY reporting_patient_follow_ups.facility_id, reporting_patient_follow_ups.month_date + ), monthly_hypertension_overdue_patients AS ( + SELECT reporting_overdue_patients.assigned_facility_region_id, + reporting_overdue_patients.month_date, + count(*) FILTER (WHERE (reporting_overdue_patients.is_overdue = 'yes'::text)) AS overdue_patients, + count(*) FILTER (WHERE ((reporting_overdue_patients.is_overdue = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text))) AS contactable_overdue_patients, + count(*) FILTER (WHERE (reporting_overdue_patients.has_called = 'yes'::text)) AS patients_called, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS patients_called_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS patients_called_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS patients_called_with_result_removed_from_list, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text))) AS contactable_patients_called, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS contactable_patients_called_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS contactable_patients_called_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS contactable_patients_called_with_result_removed_from_list, + count(*) FILTER (WHERE (reporting_overdue_patients.has_visited_following_call = 'yes'::text)) AS patients_returned_after_call, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS patients_returned_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS patients_returned_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS patients_returned_with_result_removed_from_list, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text))) AS contactable_patients_returned_after_call, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS contactable_patients_returned_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS contactable_patients_returned_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS contactable_patients_returned_with_result_removed_from_list + FROM public.reporting_overdue_patients + WHERE ((reporting_overdue_patients.hypertension = 'yes'::text) AND (reporting_overdue_patients.under_care = 'yes'::text)) + GROUP BY reporting_overdue_patients.assigned_facility_region_id, reporting_overdue_patients.month_date + ) + SELECT cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string, + rf.facility_id, + rf.facility_name, + rf.facility_type, + rf.facility_size, + rf.facility_region_id, + rf.facility_region_name, + rf.facility_region_slug, + rf.block_region_id, + rf.block_name, + rf.block_slug, + rf.district_id, + rf.district_region_id, + rf.district_name, + rf.district_slug, + rf.state_region_id, + rf.state_name, + rf.state_slug, + rf.organization_id, + rf.organization_region_id, + rf.organization_name, + rf.organization_slug, + registered_patients.cumulative_registrations, + registered_patients.monthly_registrations, + registered_diabetes_patients.cumulative_diabetes_registrations, + registered_diabetes_patients.monthly_diabetes_registrations, + registered_hypertension_and_diabetes_patients.cumulative_hypertension_and_diabetes_registrations, + registered_hypertension_and_diabetes_patients.monthly_hypertension_and_diabetes_registrations, + assigned_patients.under_care, + assigned_patients.lost_to_follow_up, + assigned_patients.dead, + assigned_patients.cumulative_assigned_patients, + assigned_diabetes_patients.diabetes_under_care, + assigned_diabetes_patients.diabetes_lost_to_follow_up, + assigned_diabetes_patients.diabetes_dead, + assigned_diabetes_patients.cumulative_assigned_diabetic_patients, + adjusted_outcomes.controlled_under_care AS adjusted_controlled_under_care, + adjusted_outcomes.uncontrolled_under_care AS adjusted_uncontrolled_under_care, + adjusted_outcomes.missed_visit_under_care AS adjusted_missed_visit_under_care, + adjusted_outcomes.visited_no_bp_under_care AS adjusted_visited_no_bp_under_care, + adjusted_outcomes.missed_visit_lost_to_follow_up AS adjusted_missed_visit_lost_to_follow_up, + adjusted_outcomes.visited_no_bp_lost_to_follow_up AS adjusted_visited_no_bp_lost_to_follow_up, + adjusted_outcomes.patients_under_care AS adjusted_patients_under_care, + adjusted_outcomes.patients_lost_to_follow_up AS adjusted_patients_lost_to_follow_up, + adjusted_diabetes_outcomes.random_bs_below_200_under_care AS adjusted_random_bs_below_200_under_care, + adjusted_diabetes_outcomes.fasting_bs_below_200_under_care AS adjusted_fasting_bs_below_200_under_care, + adjusted_diabetes_outcomes.post_prandial_bs_below_200_under_care AS adjusted_post_prandial_bs_below_200_under_care, + adjusted_diabetes_outcomes.hba1c_bs_below_200_under_care AS adjusted_hba1c_bs_below_200_under_care, + adjusted_diabetes_outcomes.bs_below_200_under_care AS adjusted_bs_below_200_under_care, + adjusted_diabetes_outcomes.random_bs_200_to_300_under_care AS adjusted_random_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.fasting_bs_200_to_300_under_care AS adjusted_fasting_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.post_prandial_bs_200_to_300_under_care AS adjusted_post_prandial_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.hba1c_bs_200_to_300_under_care AS adjusted_hba1c_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.bs_200_to_300_under_care AS adjusted_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.random_bs_over_300_under_care AS adjusted_random_bs_over_300_under_care, + adjusted_diabetes_outcomes.fasting_bs_over_300_under_care AS adjusted_fasting_bs_over_300_under_care, + adjusted_diabetes_outcomes.post_prandial_bs_over_300_under_care AS adjusted_post_prandial_bs_over_300_under_care, + adjusted_diabetes_outcomes.hba1c_bs_over_300_under_care AS adjusted_hba1c_bs_over_300_under_care, + adjusted_diabetes_outcomes.bs_over_300_under_care AS adjusted_bs_over_300_under_care, + adjusted_diabetes_outcomes.bs_missed_visit_under_care AS adjusted_bs_missed_visit_under_care, + adjusted_diabetes_outcomes.visited_no_bs_under_care AS adjusted_visited_no_bs_under_care, + adjusted_diabetes_outcomes.bs_missed_visit_lost_to_follow_up AS adjusted_bs_missed_visit_lost_to_follow_up, + adjusted_diabetes_outcomes.diabetes_patients_under_care AS adjusted_diabetes_patients_under_care, + adjusted_diabetes_outcomes.diabetes_patients_lost_to_follow_up AS adjusted_diabetes_patients_lost_to_follow_up, + monthly_cohort_outcomes.controlled AS monthly_cohort_controlled, + monthly_cohort_outcomes.uncontrolled AS monthly_cohort_uncontrolled, + monthly_cohort_outcomes.missed_visit AS monthly_cohort_missed_visit, + monthly_cohort_outcomes.visited_no_bp AS monthly_cohort_visited_no_bp, + monthly_cohort_outcomes.patients AS monthly_cohort_patients, + monthly_overdue_calls.call_results AS monthly_overdue_calls, + monthly_follow_ups.follow_ups AS monthly_follow_ups, + monthly_diabetes_follow_ups.follow_ups AS monthly_diabetes_follow_ups, + reporting_facility_appointment_scheduled_days.htn_total_appts_scheduled AS total_appts_scheduled, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_0_to_14_days AS appts_scheduled_0_to_14_days, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_15_to_31_days AS appts_scheduled_15_to_31_days, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_32_to_62_days AS appts_scheduled_32_to_62_days, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_more_than_62_days AS appts_scheduled_more_than_62_days, + reporting_facility_appointment_scheduled_days.diabetes_total_appts_scheduled, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_0_to_14_days, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_15_to_31_days, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_32_to_62_days, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_more_than_62_days, + monthly_hypertension_overdue_patients.overdue_patients, + monthly_hypertension_overdue_patients.contactable_overdue_patients, + monthly_hypertension_overdue_patients.patients_called, + monthly_hypertension_overdue_patients.contactable_patients_called, + monthly_hypertension_overdue_patients.patients_called_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.patients_called_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.patients_called_with_result_removed_from_list, + monthly_hypertension_overdue_patients.contactable_patients_called_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.contactable_patients_called_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.contactable_patients_called_with_result_removed_from_list, + monthly_hypertension_overdue_patients.patients_returned_after_call, + monthly_hypertension_overdue_patients.contactable_patients_returned_after_call, + monthly_hypertension_overdue_patients.patients_returned_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.patients_returned_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.patients_returned_with_result_removed_from_list, + monthly_hypertension_overdue_patients.contactable_patients_returned_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.contactable_patients_returned_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.contactable_patients_returned_with_result_removed_from_list + FROM ((((((((((((((public.reporting_facilities rf + JOIN public.reporting_months cal ON (true)) + LEFT JOIN registered_patients ON (((registered_patients.month_date = cal.month_date) AND (registered_patients.region_id = rf.facility_region_id)))) + LEFT JOIN registered_diabetes_patients ON (((registered_diabetes_patients.month_date = cal.month_date) AND (registered_diabetes_patients.region_id = rf.facility_region_id)))) + LEFT JOIN registered_hypertension_and_diabetes_patients ON (((registered_hypertension_and_diabetes_patients.month_date = cal.month_date) AND (registered_hypertension_and_diabetes_patients.region_id = rf.facility_region_id)))) + LEFT JOIN assigned_patients ON (((assigned_patients.month_date = cal.month_date) AND (assigned_patients.region_id = rf.facility_region_id)))) + LEFT JOIN assigned_diabetes_patients ON (((assigned_diabetes_patients.month_date = cal.month_date) AND (assigned_diabetes_patients.region_id = rf.facility_region_id)))) + LEFT JOIN adjusted_outcomes ON (((adjusted_outcomes.month_date = cal.month_date) AND (adjusted_outcomes.region_id = rf.facility_region_id)))) + LEFT JOIN adjusted_diabetes_outcomes ON (((adjusted_diabetes_outcomes.month_date = cal.month_date) AND (adjusted_diabetes_outcomes.region_id = rf.facility_region_id)))) + LEFT JOIN monthly_cohort_outcomes ON (((monthly_cohort_outcomes.month_date = cal.month_date) AND (monthly_cohort_outcomes.region_id = rf.facility_region_id)))) + LEFT JOIN monthly_overdue_calls ON (((monthly_overdue_calls.month_date = cal.month_date) AND (monthly_overdue_calls.region_id = rf.facility_region_id)))) + LEFT JOIN monthly_follow_ups ON (((monthly_follow_ups.month_date = cal.month_date) AND (monthly_follow_ups.facility_id = rf.facility_id)))) + LEFT JOIN monthly_diabetes_follow_ups ON (((monthly_diabetes_follow_ups.month_date = cal.month_date) AND (monthly_diabetes_follow_ups.facility_id = rf.facility_id)))) + LEFT JOIN public.reporting_facility_appointment_scheduled_days ON (((reporting_facility_appointment_scheduled_days.month_date = cal.month_date) AND (reporting_facility_appointment_scheduled_days.facility_id = rf.facility_id)))) + LEFT JOIN monthly_hypertension_overdue_patients ON (((monthly_hypertension_overdue_patients.month_date = cal.month_date) AND (monthly_hypertension_overdue_patients.assigned_facility_region_id = rf.facility_region_id)))) + WITH NO DATA; + SQL + + add_index "reporting_facility_states", ["block_region_id", "month_date"], name: "index_fs_block_month_date" + add_index "reporting_facility_states", ["district_region_id", "month_date"], name: "index_fs_district_month_date" + add_index "reporting_facility_states", ["month_date", "facility_region_id"], name: "index_fs_month_date_region_id", unique: true + add_index "reporting_facility_states", ["organization_region_id", "month_date"], name: "index_fs_organization_month_date" + add_index "reporting_facility_states", ["state_region_id", "month_date"], name: "index_fs_state_month_date" + end +end diff --git a/db/migrate/20251203093958_update_reporting_patient_visits_to_version4.rb b/db/migrate/20251203093958_update_reporting_patient_visits_to_version4.rb new file mode 100644 index 0000000000..43759ecc01 --- /dev/null +++ b/db/migrate/20251203093958_update_reporting_patient_visits_to_version4.rb @@ -0,0 +1,282 @@ +class UpdateReportingPatientVisitsToVersion4 < ActiveRecord::Migration[6.1] + def up + drop_view :reporting_patient_visits, materialized: true + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_patient_visits AS + SELECT DISTINCT ON (p.id, p.month_date) p.id AS patient_id, + p.month_date, + p.month, + p.quarter, + p.year, + p.month_string, + p.quarter_string, + p.assigned_facility_id, + p.registration_facility_id, + timezone('UTC'::text, timezone('UTC'::text, p.diagnosed_confirmed_at)) AS patient_recorded_at, + e.id AS encounter_id, + e.facility_id AS encounter_facility_id, + timezone('UTC'::text, timezone('UTC'::text, e.recorded_at)) AS encounter_recorded_at, + pd.id AS prescription_drug_id, + pd.facility_id AS prescription_drug_facility_id, + timezone('UTC'::text, timezone('UTC'::text, pd.recorded_at)) AS prescription_drug_recorded_at, + app.id AS appointment_id, + app.creation_facility_id AS appointment_creation_facility_id, + timezone('UTC'::text, timezone('UTC'::text, app.recorded_at)) AS appointment_recorded_at, + array_remove(ARRAY[ + CASE + WHEN (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, e.recorded_at)), 'YYYY-MM'::text) = p.month_string) THEN e.facility_id + ELSE NULL::uuid + END, + CASE + WHEN (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, pd.recorded_at)), 'YYYY-MM'::text) = p.month_string) THEN pd.facility_id + ELSE NULL::uuid + END, + CASE + WHEN (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, app.recorded_at)), 'YYYY-MM'::text) = p.month_string) THEN app.creation_facility_id + ELSE NULL::uuid + END], NULL::uuid) AS visited_facility_ids, + timezone('UTC'::text, timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at))) AS visited_at, + (((p.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.diagnosed_confirmed_at)))) * (12)::double precision) + (p.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.diagnosed_confirmed_at))))) AS months_since_registration, + (((p.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.diagnosed_confirmed_at)))) * (4)::double precision) + (p.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.diagnosed_confirmed_at))))) AS quarters_since_registration, + (((p.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at))))) * (12)::double precision) + (p.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at)))))) AS months_since_visit, + (((p.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at))))) * (4)::double precision) + (p.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at)))))) AS quarters_since_visit + FROM (((( SELECT p_1.id, + p_1.full_name, + p_1.age, + p_1.gender, + p_1.date_of_birth, + p_1.status, + p_1.created_at, + p_1.updated_at, + p_1.address_id, + p_1.age_updated_at, + p_1.device_created_at, + p_1.device_updated_at, + p_1.test_data, + p_1.registration_facility_id, + p_1.registration_user_id, + p_1.deleted_at, + p_1.contacted_by_counsellor, + p_1.could_not_contact_reason, + p_1.recorded_at, + p_1.reminder_consent, + p_1.deleted_by_user_id, + p_1.deleted_reason, + p_1.assigned_facility_id, + p_1.diagnosed_confirmed_at, + cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string + FROM (public.patients p_1 + LEFT JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p_1.diagnosed_confirmed_at)), 'YYYY-MM'::text) <= cal.month_string)))) p + LEFT JOIN LATERAL ( SELECT timezone('UTC'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), (encounters.encountered_on)::timestamp without time zone)) AS recorded_at, + encounters.id, + encounters.facility_id, + encounters.patient_id, + encounters.encountered_on, + encounters.timezone_offset, + encounters.notes, + encounters.metadata, + encounters.device_created_at, + encounters.device_updated_at, + encounters.deleted_at, + encounters.created_at, + encounters.updated_at + FROM public.encounters + WHERE ((encounters.patient_id = p.id) AND (to_char((encounters.encountered_on)::timestamp with time zone, 'YYYY-MM'::text) <= p.month_string) AND (encounters.deleted_at IS NULL)) + ORDER BY encounters.encountered_on DESC + LIMIT 1) e ON (true)) + LEFT JOIN LATERAL ( SELECT prescription_drugs.device_created_at AS recorded_at, + prescription_drugs.id, + prescription_drugs.name, + prescription_drugs.rxnorm_code, + prescription_drugs.dosage, + prescription_drugs.device_created_at, + prescription_drugs.device_updated_at, + prescription_drugs.created_at, + prescription_drugs.updated_at, + prescription_drugs.patient_id, + prescription_drugs.facility_id, + prescription_drugs.is_protocol_drug, + prescription_drugs.is_deleted, + prescription_drugs.deleted_at, + prescription_drugs.user_id, + prescription_drugs.frequency, + prescription_drugs.duration_in_days, + prescription_drugs.teleconsultation_id + FROM public.prescription_drugs + WHERE ((prescription_drugs.patient_id = p.id) AND (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, prescription_drugs.device_created_at)), 'YYYY-MM'::text) <= p.month_string) AND (prescription_drugs.deleted_at IS NULL)) + ORDER BY prescription_drugs.device_created_at DESC + LIMIT 1) pd ON (true)) + LEFT JOIN LATERAL ( SELECT appointments.device_created_at AS recorded_at, + appointments.id, + appointments.patient_id, + appointments.facility_id, + appointments.scheduled_date, + appointments.status, + appointments.cancel_reason, + appointments.device_created_at, + appointments.device_updated_at, + appointments.created_at, + appointments.updated_at, + appointments.remind_on, + appointments.agreed_to_visit, + appointments.deleted_at, + appointments.appointment_type, + appointments.user_id, + appointments.creation_facility_id + FROM public.appointments + WHERE ((appointments.patient_id = p.id) AND (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, appointments.device_created_at)), 'YYYY-MM'::text) <= p.month_string) AND (appointments.deleted_at IS NULL)) + ORDER BY appointments.device_created_at DESC + LIMIT 1) app ON (true)) + WHERE (p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) + ORDER BY p.id, p.month_date, (timezone('UTC'::text, timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at)))) DESC + WITH NO DATA; + SQL + + add_index "reporting_patient_visits", ["month_date", "patient_id"], name: "patient_visits_patient_id_month_date", unique: true + end + + def down + drop_view :reporting_patient_visits, materialized: true + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_patient_visits AS + SELECT DISTINCT ON (p.id, p.month_date) p.id AS patient_id, + p.month_date, + p.month, + p.quarter, + p.year, + p.month_string, + p.quarter_string, + p.assigned_facility_id, + p.registration_facility_id, + timezone('UTC'::text, timezone('UTC'::text, p.recorded_at)) AS patient_recorded_at, + e.id AS encounter_id, + e.facility_id AS encounter_facility_id, + timezone('UTC'::text, timezone('UTC'::text, e.recorded_at)) AS encounter_recorded_at, + pd.id AS prescription_drug_id, + pd.facility_id AS prescription_drug_facility_id, + timezone('UTC'::text, timezone('UTC'::text, pd.recorded_at)) AS prescription_drug_recorded_at, + app.id AS appointment_id, + app.creation_facility_id AS appointment_creation_facility_id, + timezone('UTC'::text, timezone('UTC'::text, app.recorded_at)) AS appointment_recorded_at, + array_remove(ARRAY[ + CASE + WHEN (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, e.recorded_at)), 'YYYY-MM'::text) = p.month_string) THEN e.facility_id + ELSE NULL::uuid + END, + CASE + WHEN (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, pd.recorded_at)), 'YYYY-MM'::text) = p.month_string) THEN pd.facility_id + ELSE NULL::uuid + END, + CASE + WHEN (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, app.recorded_at)), 'YYYY-MM'::text) = p.month_string) THEN app.creation_facility_id + ELSE NULL::uuid + END], NULL::uuid) AS visited_facility_ids, + timezone('UTC'::text, timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at))) AS visited_at, + (((p.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.recorded_at)))) * (12)::double precision) + (p.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.recorded_at))))) AS months_since_registration, + (((p.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.recorded_at)))) * (4)::double precision) + (p.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.recorded_at))))) AS quarters_since_registration, + (((p.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at))))) * (12)::double precision) + (p.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at)))))) AS months_since_visit, + (((p.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at))))) * (4)::double precision) + (p.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at)))))) AS quarters_since_visit + FROM (((( SELECT p_1.id, + p_1.full_name, + p_1.age, + p_1.gender, + p_1.date_of_birth, + p_1.status, + p_1.created_at, + p_1.updated_at, + p_1.address_id, + p_1.age_updated_at, + p_1.device_created_at, + p_1.device_updated_at, + p_1.test_data, + p_1.registration_facility_id, + p_1.registration_user_id, + p_1.deleted_at, + p_1.contacted_by_counsellor, + p_1.could_not_contact_reason, + p_1.recorded_at, + p_1.reminder_consent, + p_1.deleted_by_user_id, + p_1.deleted_reason, + p_1.assigned_facility_id, + cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string + FROM (public.patients p_1 + LEFT JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p_1.recorded_at)), 'YYYY-MM'::text) <= cal.month_string)))) p + LEFT JOIN LATERAL ( SELECT timezone('UTC'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), (encounters.encountered_on)::timestamp without time zone)) AS recorded_at, + encounters.id, + encounters.facility_id, + encounters.patient_id, + encounters.encountered_on, + encounters.timezone_offset, + encounters.notes, + encounters.metadata, + encounters.device_created_at, + encounters.device_updated_at, + encounters.deleted_at, + encounters.created_at, + encounters.updated_at + FROM public.encounters + WHERE ((encounters.patient_id = p.id) AND (to_char((encounters.encountered_on)::timestamp with time zone, 'YYYY-MM'::text) <= p.month_string) AND (encounters.deleted_at IS NULL)) + ORDER BY encounters.encountered_on DESC + LIMIT 1) e ON (true)) + LEFT JOIN LATERAL ( SELECT prescription_drugs.device_created_at AS recorded_at, + prescription_drugs.id, + prescription_drugs.name, + prescription_drugs.rxnorm_code, + prescription_drugs.dosage, + prescription_drugs.device_created_at, + prescription_drugs.device_updated_at, + prescription_drugs.created_at, + prescription_drugs.updated_at, + prescription_drugs.patient_id, + prescription_drugs.facility_id, + prescription_drugs.is_protocol_drug, + prescription_drugs.is_deleted, + prescription_drugs.deleted_at, + prescription_drugs.user_id, + prescription_drugs.frequency, + prescription_drugs.duration_in_days, + prescription_drugs.teleconsultation_id + FROM public.prescription_drugs + WHERE ((prescription_drugs.patient_id = p.id) AND (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, prescription_drugs.device_created_at)), 'YYYY-MM'::text) <= p.month_string) AND (prescription_drugs.deleted_at IS NULL)) + ORDER BY prescription_drugs.device_created_at DESC + LIMIT 1) pd ON (true)) + LEFT JOIN LATERAL ( SELECT appointments.device_created_at AS recorded_at, + appointments.id, + appointments.patient_id, + appointments.facility_id, + appointments.scheduled_date, + appointments.status, + appointments.cancel_reason, + appointments.device_created_at, + appointments.device_updated_at, + appointments.created_at, + appointments.updated_at, + appointments.remind_on, + appointments.agreed_to_visit, + appointments.deleted_at, + appointments.appointment_type, + appointments.user_id, + appointments.creation_facility_id + FROM public.appointments + WHERE ((appointments.patient_id = p.id) AND (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, appointments.device_created_at)), 'YYYY-MM'::text) <= p.month_string) AND (appointments.deleted_at IS NULL)) + ORDER BY appointments.device_created_at DESC + LIMIT 1) app ON (true)) + WHERE (p.deleted_at IS NULL) + ORDER BY p.id, p.month_date, (timezone('UTC'::text, timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at)))) DESC + WITH NO DATA; + SQL + + add_index "reporting_patient_visits", ["month_date", "patient_id"], name: "patient_visits_patient_id_month_date", unique: true + end +end diff --git a/db/migrate/20251204092000_update_reporting_prescriptions_to_version4.rb b/db/migrate/20251204092000_update_reporting_prescriptions_to_version4.rb new file mode 100644 index 0000000000..0e593b0ada --- /dev/null +++ b/db/migrate/20251204092000_update_reporting_prescriptions_to_version4.rb @@ -0,0 +1,133 @@ +class UpdateReportingPrescriptionsToVersion4 < ActiveRecord::Migration[6.1] + def up + drop_view :reporting_prescriptions, materialized: true + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_prescriptions AS + SELECT p.id AS patient_id, + p.month_date, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Amlodipine'::text)), (0)::double precision) AS amlodipine, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Telmisartan'::text)), (0)::double precision) AS telmisartan, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Losartan Potassium'::text)), (0)::double precision) AS losartan, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Atenolol'::text)), (0)::double precision) AS atenolol, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Enalapril'::text)), (0)::double precision) AS enalapril, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Chlorthalidone'::text)), (0)::double precision) AS chlorthalidone, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Hydrochlorothiazide'::text)), (0)::double precision) AS hydrochlorothiazide, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE (((prescriptions.clean_name)::text <> ALL (ARRAY[('Amlodipine'::character varying)::text, ('Telmisartan'::character varying)::text, ('Losartan'::character varying)::text, ('Atenolol'::character varying)::text, ('Enalapril'::character varying)::text, ('Chlorthalidone'::character varying)::text, ('Hydrochlorothiazide'::character varying)::text])) AND (prescriptions.medicine_purpose_hypertension = true))), (0)::double precision) AS other_bp_medications + FROM (( SELECT p_1.id, + p_1.full_name, + p_1.age, + p_1.gender, + p_1.date_of_birth, + p_1.status, + p_1.created_at, + p_1.updated_at, + p_1.address_id, + p_1.age_updated_at, + p_1.device_created_at, + p_1.device_updated_at, + p_1.test_data, + p_1.registration_facility_id, + p_1.registration_user_id, + p_1.deleted_at, + p_1.contacted_by_counsellor, + p_1.could_not_contact_reason, + p_1.recorded_at, + p_1.reminder_consent, + p_1.deleted_by_user_id, + p_1.deleted_reason, + p_1.assigned_facility_id, + cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string + FROM (public.patients p_1 + LEFT JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p_1.diagnosed_confirmed_at)), 'YYYY-MM'::text) <= cal.month_string))) + WHERE (p_1.deleted_at IS NULL AND p_1.diagnosed_confirmed_at IS NOT NULL)) p + LEFT JOIN LATERAL ( SELECT DISTINCT ON (clean.medicine) actual.name AS actual_name, + actual.dosage AS actual_dosage, + clean.medicine AS clean_name, + clean.dosage AS clean_dosage, + purpose.hypertension AS medicine_purpose_hypertension, + purpose.diabetes AS medicine_purpose_diabetes + FROM (((public.prescription_drugs actual + LEFT JOIN public.raw_to_clean_medicines raw ON (((lower(regexp_replace((raw.raw_name)::text, '\s+'::text, ''::text, 'g'::text)) = lower(regexp_replace((actual.name)::text, '\s+'::text, ''::text, 'g'::text))) AND (lower(regexp_replace((raw.raw_dosage)::text, '\s+'::text, ''::text, 'g'::text)) = lower(regexp_replace((actual.dosage)::text, '\s+'::text, ''::text, 'g'::text)))))) + LEFT JOIN public.clean_medicine_to_dosages clean ON ((clean.rxcui = raw.rxcui))) + LEFT JOIN public.medicine_purposes purpose ON (((clean.medicine)::text = (purpose.name)::text))) + WHERE ((actual.patient_id = p.id) AND (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, actual.device_created_at)), 'YYYY-MM'::text) <= p.month_string) AND (actual.deleted_at IS NULL) AND ((actual.is_deleted = false) OR ((actual.is_deleted = true) AND (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, actual.device_updated_at)), 'YYYY-MM'::text) > p.month_string)))) + ORDER BY clean.medicine, actual.device_created_at DESC) prescriptions ON (true)) + GROUP BY p.id, p.month_date + WITH NO DATA; + SQL + + add_index :reporting_prescriptions, [:patient_id, :month_date], name: "reporting_prescriptions_patient_month_date", unique: true + add_index :reporting_prescriptions, [:month_date], name: "reporting_prescriptions_month_date" + end + + def down + drop_view :reporting_prescriptions, materialized: true + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_prescriptions AS + SELECT p.id AS patient_id, + p.month_date, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Amlodipine'::text)), (0)::double precision) AS amlodipine, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Telmisartan'::text)), (0)::double precision) AS telmisartan, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Losartan Potassium'::text)), (0)::double precision) AS losartan, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Atenolol'::text)), (0)::double precision) AS atenolol, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Enalapril'::text)), (0)::double precision) AS enalapril, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Chlorthalidone'::text)), (0)::double precision) AS chlorthalidone, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Hydrochlorothiazide'::text)), (0)::double precision) AS hydrochlorothiazide, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE (((prescriptions.clean_name)::text <> ALL (ARRAY[('Amlodipine'::character varying)::text, ('Telmisartan'::character varying)::text, ('Losartan'::character varying)::text, ('Atenolol'::character varying)::text, ('Enalapril'::character varying)::text, ('Chlorthalidone'::character varying)::text, ('Hydrochlorothiazide'::character varying)::text])) AND (prescriptions.medicine_purpose_hypertension = true))), (0)::double precision) AS other_bp_medications + FROM (( SELECT p_1.id, + p_1.full_name, + p_1.age, + p_1.gender, + p_1.date_of_birth, + p_1.status, + p_1.created_at, + p_1.updated_at, + p_1.address_id, + p_1.age_updated_at, + p_1.device_created_at, + p_1.device_updated_at, + p_1.test_data, + p_1.registration_facility_id, + p_1.registration_user_id, + p_1.deleted_at, + p_1.contacted_by_counsellor, + p_1.could_not_contact_reason, + p_1.recorded_at, + p_1.reminder_consent, + p_1.deleted_by_user_id, + p_1.deleted_reason, + p_1.assigned_facility_id, + cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string + FROM (public.patients p_1 + LEFT JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p_1.recorded_at)), 'YYYY-MM'::text) <= cal.month_string))) + WHERE (p_1.deleted_at IS NULL)) p + LEFT JOIN LATERAL ( SELECT DISTINCT ON (clean.medicine) actual.name AS actual_name, + actual.dosage AS actual_dosage, + clean.medicine AS clean_name, + clean.dosage AS clean_dosage, + purpose.hypertension AS medicine_purpose_hypertension, + purpose.diabetes AS medicine_purpose_diabetes + FROM (((public.prescription_drugs actual + LEFT JOIN public.raw_to_clean_medicines raw ON (((lower(regexp_replace((raw.raw_name)::text, '\s+'::text, ''::text, 'g'::text)) = lower(regexp_replace((actual.name)::text, '\s+'::text, ''::text, 'g'::text))) AND (lower(regexp_replace((raw.raw_dosage)::text, '\s+'::text, ''::text, 'g'::text)) = lower(regexp_replace((actual.dosage)::text, '\s+'::text, ''::text, 'g'::text)))))) + LEFT JOIN public.clean_medicine_to_dosages clean ON ((clean.rxcui = raw.rxcui))) + LEFT JOIN public.medicine_purposes purpose ON (((clean.medicine)::text = (purpose.name)::text))) + WHERE ((actual.patient_id = p.id) AND (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, actual.device_created_at)), 'YYYY-MM'::text) <= p.month_string) AND (actual.deleted_at IS NULL) AND ((actual.is_deleted = false) OR ((actual.is_deleted = true) AND (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, actual.device_updated_at)), 'YYYY-MM'::text) > p.month_string)))) + ORDER BY clean.medicine, actual.device_created_at DESC) prescriptions ON (true)) + GROUP BY p.id, p.month_date + WITH NO DATA; + SQL + + add_index :reporting_prescriptions, [:patient_id, :month_date], name: "reporting_prescriptions_patient_month_date", unique: true + add_index :reporting_prescriptions, [:month_date], name: "reporting_prescriptions_month_date" + end +end diff --git a/db/migrate/20251205091911_update_reporting_patient_follow_ups_to_version5.rb b/db/migrate/20251205091911_update_reporting_patient_follow_ups_to_version5.rb new file mode 100644 index 0000000000..10d85bb5f0 --- /dev/null +++ b/db/migrate/20251205091911_update_reporting_patient_follow_ups_to_version5.rb @@ -0,0 +1,1043 @@ +class UpdateReportingPatientFollowUpsToVersion5 < ActiveRecord::Migration[6.1] + def up + drop_view :reporting_facility_states, materialized: true + drop_view :reporting_facility_monthly_follow_ups_and_registrations, materialized: true + drop_view :reporting_patient_follow_ups, materialized: true + + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_patient_follow_ups AS + WITH follow_up_blood_pressures AS ( + SELECT DISTINCT ON (p.id, bp.facility_id, bp.user_id, (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)), 'YYYY-MM'::text))) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + bp.id AS visit_id, + 'BloodPressure'::text AS visit_type, + bp.facility_id, + bp.user_id, + bp.recorded_at AS visited_at, + to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)), 'YYYY-MM'::text) AS month_string + FROM (public.patients p + JOIN public.blood_pressures bp ON (((p.id = bp.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))))) + WHERE (p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) + ), follow_up_blood_sugars AS ( + SELECT DISTINCT ON (p.id, bs.facility_id, bs.user_id, (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)), 'YYYY-MM'::text))) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + bs.id AS visit_id, + 'BloodSugar'::text AS visit_type, + bs.facility_id, + bs.user_id, + bs.recorded_at AS visited_at, + to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)), 'YYYY-MM'::text) AS month_string + FROM (public.patients p + JOIN public.blood_sugars bs ON (((p.id = bs.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))))) + WHERE (p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) + ), follow_up_prescription_drugs AS ( + SELECT DISTINCT ON (p.id, pd.facility_id, pd.user_id, (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, pd.device_created_at)), 'YYYY-MM'::text))) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + pd.id AS visit_id, + 'PrescriptionDrug'::text AS visit_type, + pd.facility_id, + pd.user_id, + pd.device_created_at AS visited_at, + to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, pd.device_created_at)), 'YYYY-MM'::text) AS month_string + FROM (public.patients p + JOIN public.prescription_drugs pd ON (((p.id = pd.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, pd.device_created_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))))) + WHERE (p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) + ), follow_up_appointments AS ( + SELECT DISTINCT ON (p.id, app.creation_facility_id, app.user_id, (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, app.device_created_at)), 'YYYY-MM'::text))) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + app.id AS visit_id, + 'Appointment'::text AS visit_type, + app.creation_facility_id AS facility_id, + app.user_id, + app.device_created_at AS visited_at, + to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, app.device_created_at)), 'YYYY-MM'::text) AS month_string + FROM (public.patients p + JOIN public.appointments app ON (((p.id = app.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, app.device_created_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))))) + WHERE (p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) + ), all_follow_ups AS ( + SELECT follow_up_blood_pressures.patient_id, + follow_up_blood_pressures.patient_gender, + follow_up_blood_pressures.visit_id, + follow_up_blood_pressures.visit_type, + follow_up_blood_pressures.facility_id, + follow_up_blood_pressures.user_id, + follow_up_blood_pressures.visited_at, + follow_up_blood_pressures.month_string + FROM follow_up_blood_pressures + UNION + SELECT follow_up_blood_sugars.patient_id, + follow_up_blood_sugars.patient_gender, + follow_up_blood_sugars.visit_id, + follow_up_blood_sugars.visit_type, + follow_up_blood_sugars.facility_id, + follow_up_blood_sugars.user_id, + follow_up_blood_sugars.visited_at, + follow_up_blood_sugars.month_string + FROM follow_up_blood_sugars + UNION + SELECT follow_up_prescription_drugs.patient_id, + follow_up_prescription_drugs.patient_gender, + follow_up_prescription_drugs.visit_id, + follow_up_prescription_drugs.visit_type, + follow_up_prescription_drugs.facility_id, + follow_up_prescription_drugs.user_id, + follow_up_prescription_drugs.visited_at, + follow_up_prescription_drugs.month_string + FROM follow_up_prescription_drugs + UNION + SELECT follow_up_appointments.patient_id, + follow_up_appointments.patient_gender, + follow_up_appointments.visit_id, + follow_up_appointments.visit_type, + follow_up_appointments.facility_id, + follow_up_appointments.user_id, + follow_up_appointments.visited_at, + follow_up_appointments.month_string + FROM follow_up_appointments + ) + SELECT DISTINCT ON (cal.month_string, all_follow_ups.facility_id, all_follow_ups.user_id, all_follow_ups.patient_id) all_follow_ups.patient_id, + all_follow_ups.patient_gender, + all_follow_ups.facility_id, + mh.diabetes, + mh.hypertension, + all_follow_ups.user_id, + all_follow_ups.visit_id, + all_follow_ups.visit_type, + all_follow_ups.visited_at, + cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string + FROM ((all_follow_ups + JOIN public.medical_histories mh ON ((all_follow_ups.patient_id = mh.patient_id))) + LEFT JOIN public.reporting_months cal ON ((all_follow_ups.month_string = cal.month_string))) + ORDER BY cal.month_string DESC + WITH NO DATA; + SQL + + add_index :reporting_patient_follow_ups, [:facility_id], name: "index_reporting_patient_follow_ups_on_facility_id" + add_index :reporting_patient_follow_ups, [:patient_id, :user_id, :facility_id, :month_date], unique: true, name: "reporting_patient_follow_ups_unique_index" + + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_facility_monthly_follow_ups_and_registrations AS + WITH monthly_registration_patient_states AS ( + SELECT reporting_patient_states.registration_facility_id AS facility_id, + reporting_patient_states.month_date, + reporting_patient_states.gender, + reporting_patient_states.hypertension, + reporting_patient_states.diabetes + FROM public.reporting_patient_states + WHERE (reporting_patient_states.months_since_registration = (0)::double precision) + ), registered_patients AS ( + SELECT monthly_registration_patient_states.facility_id, + monthly_registration_patient_states.month_date, + count(*) AS monthly_registrations_all, + count(*) FILTER (WHERE (monthly_registration_patient_states.hypertension = 'yes'::text)) AS monthly_registrations_htn_all, + count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'female'::text))) AS monthly_registrations_htn_female, + count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'male'::text))) AS monthly_registrations_htn_male, + count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'transgender'::text))) AS monthly_registrations_htn_transgender, + count(*) FILTER (WHERE (monthly_registration_patient_states.diabetes = 'yes'::text)) AS monthly_registrations_dm_all, + count(*) FILTER (WHERE ((monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'female'::text))) AS monthly_registrations_dm_female, + count(*) FILTER (WHERE ((monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'male'::text))) AS monthly_registrations_dm_male, + count(*) FILTER (WHERE ((monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'transgender'::text))) AS monthly_registrations_dm_transgender, + count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) OR (monthly_registration_patient_states.diabetes = 'yes'::text))) AS monthly_registrations_htn_or_dm, + count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text))) AS monthly_registrations_htn_and_dm, + count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'female'::text))) AS monthly_registrations_htn_and_dm_female, + count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'male'::text))) AS monthly_registrations_htn_and_dm_male, + count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'transgender'::text))) AS monthly_registrations_htn_and_dm_transgender, + (count(*) FILTER (WHERE (monthly_registration_patient_states.hypertension = 'yes'::text)) - count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text)))) AS monthly_registrations_htn_only, + (count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'female'::text))) - count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'female'::text)))) AS monthly_registrations_htn_only_female, + (count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'male'::text))) - count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'male'::text)))) AS monthly_registrations_htn_only_male, + (count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'transgender'::text))) - count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'transgender'::text)))) AS monthly_registrations_htn_only_transgender, + (count(*) FILTER (WHERE (monthly_registration_patient_states.diabetes = 'yes'::text)) - count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text)))) AS monthly_registrations_dm_only, + (count(*) FILTER (WHERE ((monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'female'::text))) - count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'female'::text)))) AS monthly_registrations_dm_only_female, + (count(*) FILTER (WHERE ((monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'male'::text))) - count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'male'::text)))) AS monthly_registrations_dm_only_male, + (count(*) FILTER (WHERE ((monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'transgender'::text))) - count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'transgender'::text)))) AS monthly_registrations_dm_only_transgender + FROM monthly_registration_patient_states + GROUP BY monthly_registration_patient_states.facility_id, monthly_registration_patient_states.month_date + ), follow_ups AS ( + SELECT reporting_patient_follow_ups.facility_id, + reporting_patient_follow_ups.month_date, + count(DISTINCT reporting_patient_follow_ups.patient_id) AS monthly_follow_ups_all, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE (reporting_patient_follow_ups.hypertension = 'yes'::text)) AS monthly_follow_ups_htn_all, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'female'::public.gender_enum))) AS monthly_follow_ups_htn_female, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'male'::public.gender_enum))) AS monthly_follow_ups_htn_male, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'transgender'::public.gender_enum))) AS monthly_follow_ups_htn_transgender, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE (reporting_patient_follow_ups.diabetes = 'yes'::text)) AS monthly_follow_ups_dm_all, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'female'::public.gender_enum))) AS monthly_follow_ups_dm_female, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'male'::public.gender_enum))) AS monthly_follow_ups_dm_male, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'transgender'::public.gender_enum))) AS monthly_follow_ups_dm_transgender, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) OR (reporting_patient_follow_ups.diabetes = 'yes'::text))) AS monthly_follow_ups_htn_or_dm, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text))) AS monthly_follow_ups_htn_and_dm, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'female'::public.gender_enum))) AS monthly_follow_ups_htn_and_dm_female, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'male'::public.gender_enum))) AS monthly_follow_ups_htn_and_dm_male, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'transgender'::public.gender_enum))) AS monthly_follow_ups_htn_and_dm_transgender, + (count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE (reporting_patient_follow_ups.hypertension = 'yes'::text)) - count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text)))) AS monthly_follow_ups_htn_only, + (count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'female'::public.gender_enum))) - count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'female'::public.gender_enum)))) AS monthly_follow_ups_htn_only_female, + (count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'male'::public.gender_enum))) - count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'male'::public.gender_enum)))) AS monthly_follow_ups_htn_only_male, + (count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'transgender'::public.gender_enum))) - count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'transgender'::public.gender_enum)))) AS monthly_follow_ups_htn_only_transgender, + (count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE (reporting_patient_follow_ups.diabetes = 'yes'::text)) - count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text)))) AS monthly_follow_ups_dm_only, + (count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'female'::public.gender_enum))) - count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'female'::public.gender_enum)))) AS monthly_follow_ups_dm_only_female, + (count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'male'::public.gender_enum))) - count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'male'::public.gender_enum)))) AS monthly_follow_ups_dm_only_male, + (count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'transgender'::public.gender_enum))) - count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'transgender'::public.gender_enum)))) AS monthly_follow_ups_dm_only_transgender + FROM public.reporting_patient_follow_ups + GROUP BY reporting_patient_follow_ups.facility_id, reporting_patient_follow_ups.month_date + ) + SELECT rf.facility_region_slug, + rf.facility_id, + rf.facility_region_id, + rf.block_region_id, + rf.district_region_id, + rf.state_region_id, + cal.month_date, + COALESCE(registered_patients.monthly_registrations_all, (0)::bigint) AS monthly_registrations_all, + COALESCE(registered_patients.monthly_registrations_htn_all, (0)::bigint) AS monthly_registrations_htn_all, + COALESCE(registered_patients.monthly_registrations_htn_male, (0)::bigint) AS monthly_registrations_htn_male, + COALESCE(registered_patients.monthly_registrations_htn_female, (0)::bigint) AS monthly_registrations_htn_female, + COALESCE(registered_patients.monthly_registrations_htn_transgender, (0)::bigint) AS monthly_registrations_htn_transgender, + COALESCE(registered_patients.monthly_registrations_dm_all, (0)::bigint) AS monthly_registrations_dm_all, + COALESCE(registered_patients.monthly_registrations_dm_male, (0)::bigint) AS monthly_registrations_dm_male, + COALESCE(registered_patients.monthly_registrations_dm_female, (0)::bigint) AS monthly_registrations_dm_female, + COALESCE(registered_patients.monthly_registrations_dm_transgender, (0)::bigint) AS monthly_registrations_dm_transgender, + COALESCE(follow_ups.monthly_follow_ups_all, (0)::bigint) AS monthly_follow_ups_all, + COALESCE(follow_ups.monthly_follow_ups_htn_all, (0)::bigint) AS monthly_follow_ups_htn_all, + COALESCE(follow_ups.monthly_follow_ups_htn_female, (0)::bigint) AS monthly_follow_ups_htn_female, + COALESCE(follow_ups.monthly_follow_ups_htn_male, (0)::bigint) AS monthly_follow_ups_htn_male, + COALESCE(follow_ups.monthly_follow_ups_htn_transgender, (0)::bigint) AS monthly_follow_ups_htn_transgender, + COALESCE(follow_ups.monthly_follow_ups_dm_all, (0)::bigint) AS monthly_follow_ups_dm_all, + COALESCE(follow_ups.monthly_follow_ups_dm_female, (0)::bigint) AS monthly_follow_ups_dm_female, + COALESCE(follow_ups.monthly_follow_ups_dm_male, (0)::bigint) AS monthly_follow_ups_dm_male, + COALESCE(follow_ups.monthly_follow_ups_dm_transgender, (0)::bigint) AS monthly_follow_ups_dm_transgender, + COALESCE(registered_patients.monthly_registrations_htn_or_dm, (0)::bigint) AS monthly_registrations_htn_or_dm, + COALESCE(registered_patients.monthly_registrations_htn_only, (0)::bigint) AS monthly_registrations_htn_only, + COALESCE(registered_patients.monthly_registrations_htn_only_male, (0)::bigint) AS monthly_registrations_htn_only_male, + COALESCE(registered_patients.monthly_registrations_htn_only_female, (0)::bigint) AS monthly_registrations_htn_only_female, + COALESCE(registered_patients.monthly_registrations_htn_only_transgender, (0)::bigint) AS monthly_registrations_htn_only_transgender, + COALESCE(registered_patients.monthly_registrations_dm_only, (0)::bigint) AS monthly_registrations_dm_only, + COALESCE(registered_patients.monthly_registrations_dm_only_male, (0)::bigint) AS monthly_registrations_dm_only_male, + COALESCE(registered_patients.monthly_registrations_dm_only_female, (0)::bigint) AS monthly_registrations_dm_only_female, + COALESCE(registered_patients.monthly_registrations_dm_only_transgender, (0)::bigint) AS monthly_registrations_dm_only_transgender, + COALESCE(registered_patients.monthly_registrations_htn_and_dm, (0)::bigint) AS monthly_registrations_htn_and_dm, + COALESCE(registered_patients.monthly_registrations_htn_and_dm_male, (0)::bigint) AS monthly_registrations_htn_and_dm_male, + COALESCE(registered_patients.monthly_registrations_htn_and_dm_female, (0)::bigint) AS monthly_registrations_htn_and_dm_female, + COALESCE(registered_patients.monthly_registrations_htn_and_dm_transgender, (0)::bigint) AS monthly_registrations_htn_and_dm_transgender, + COALESCE(follow_ups.monthly_follow_ups_htn_or_dm, (0)::bigint) AS monthly_follow_ups_htn_or_dm, + COALESCE(follow_ups.monthly_follow_ups_htn_only, (0)::bigint) AS monthly_follow_ups_htn_only, + COALESCE(follow_ups.monthly_follow_ups_htn_only_female, (0)::bigint) AS monthly_follow_ups_htn_only_female, + COALESCE(follow_ups.monthly_follow_ups_htn_only_male, (0)::bigint) AS monthly_follow_ups_htn_only_male, + COALESCE(follow_ups.monthly_follow_ups_htn_only_transgender, (0)::bigint) AS monthly_follow_ups_htn_only_transgender, + COALESCE(follow_ups.monthly_follow_ups_dm_only, (0)::bigint) AS monthly_follow_ups_dm_only, + COALESCE(follow_ups.monthly_follow_ups_dm_only_female, (0)::bigint) AS monthly_follow_ups_dm_only_female, + COALESCE(follow_ups.monthly_follow_ups_dm_only_male, (0)::bigint) AS monthly_follow_ups_dm_only_male, + COALESCE(follow_ups.monthly_follow_ups_dm_only_transgender, (0)::bigint) AS monthly_follow_ups_dm_only_transgender, + COALESCE(follow_ups.monthly_follow_ups_htn_and_dm, (0)::bigint) AS monthly_follow_ups_htn_and_dm, + COALESCE(follow_ups.monthly_follow_ups_htn_and_dm_male, (0)::bigint) AS monthly_follow_ups_htn_and_dm_male, + COALESCE(follow_ups.monthly_follow_ups_htn_and_dm_female, (0)::bigint) AS monthly_follow_ups_htn_and_dm_female, + COALESCE(follow_ups.monthly_follow_ups_htn_and_dm_transgender, (0)::bigint) AS monthly_follow_ups_htn_and_dm_transgender + FROM (((public.reporting_facilities rf + JOIN public.reporting_months cal ON (true)) + LEFT JOIN registered_patients ON (((registered_patients.month_date = cal.month_date) AND (registered_patients.facility_id = rf.facility_id)))) + LEFT JOIN follow_ups ON (((follow_ups.month_date = cal.month_date) AND (follow_ups.facility_id = rf.facility_id)))) + ORDER BY cal.month_date DESC + WITH NO DATA; + SQL + + add_index :reporting_facility_monthly_follow_ups_and_registrations, [:facility_id], name: "facility_monthly_fr_facility_id" + add_index :reporting_facility_monthly_follow_ups_and_registrations, [:facility_region_id], name: "facility_monthly_fr_facility_region_id" + add_index :reporting_facility_monthly_follow_ups_and_registrations, [:month_date, :facility_region_id], unique: true, name: "facility_monthly_fr_month_date_facility_region_id" + + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_facility_states AS + WITH registered_patients AS ( + SELECT reporting_patient_states.registration_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(*) AS cumulative_registrations, + count(*) FILTER (WHERE (reporting_patient_states.months_since_registration = (0)::double precision)) AS monthly_registrations + FROM public.reporting_patient_states + WHERE (reporting_patient_states.hypertension = 'yes'::text) + GROUP BY reporting_patient_states.registration_facility_region_id, reporting_patient_states.month_date + ), registered_diabetes_patients AS ( + SELECT reporting_patient_states.registration_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(*) AS cumulative_diabetes_registrations, + count(*) FILTER (WHERE (reporting_patient_states.months_since_registration = (0)::double precision)) AS monthly_diabetes_registrations + FROM public.reporting_patient_states + WHERE (reporting_patient_states.diabetes = 'yes'::text) + GROUP BY reporting_patient_states.registration_facility_region_id, reporting_patient_states.month_date + ), registered_hypertension_and_diabetes_patients AS ( + SELECT reporting_patient_states.registration_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(*) AS cumulative_hypertension_and_diabetes_registrations, + count(*) FILTER (WHERE (reporting_patient_states.months_since_registration = (0)::double precision)) AS monthly_hypertension_and_diabetes_registrations + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.hypertension = 'yes'::text) AND (reporting_patient_states.diabetes = 'yes'::text)) + GROUP BY reporting_patient_states.registration_facility_region_id, reporting_patient_states.month_date + ), assigned_patients AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'dead'::text)) AS dead, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state <> 'dead'::text)) AS cumulative_assigned_patients + FROM public.reporting_patient_states + WHERE (reporting_patient_states.hypertension = 'yes'::text) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), assigned_diabetes_patients AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS diabetes_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS diabetes_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'dead'::text)) AS diabetes_dead, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state <> 'dead'::text)) AS cumulative_assigned_diabetic_patients + FROM public.reporting_patient_states + WHERE (reporting_patient_states.diabetes = 'yes'::text) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), adjusted_outcomes AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'controlled'::text))) AS controlled_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'uncontrolled'::text))) AS uncontrolled_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS missed_visit_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'visited_no_bp'::text))) AS visited_no_bp_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS missed_visit_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'visited_no_bp'::text))) AS visited_no_bp_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS patients_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS patients_lost_to_follow_up + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.hypertension = 'yes'::text) AND (reporting_patient_states.months_since_registration >= (3)::double precision)) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), adjusted_diabetes_outcomes AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'random'::text))) AS random_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'post_prandial'::text))) AS post_prandial_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'fasting'::text))) AS fasting_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'hba1c'::text))) AS hba1c_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text))) AS bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'random'::text))) AS random_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'post_prandial'::text))) AS post_prandial_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'fasting'::text))) AS fasting_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'hba1c'::text))) AS hba1c_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text))) AS bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'random'::text))) AS random_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'post_prandial'::text))) AS post_prandial_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'fasting'::text))) AS fasting_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'hba1c'::text))) AS hba1c_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text))) AS bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS bs_missed_visit_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'visited_no_bs'::text))) AS visited_no_bs_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS bs_missed_visit_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS diabetes_patients_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS diabetes_patients_lost_to_follow_up + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.diabetes = 'yes'::text) AND (reporting_patient_states.months_since_registration >= (3)::double precision)) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), monthly_cohort_outcomes AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'controlled'::text)) AS controlled, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'uncontrolled'::text)) AS uncontrolled, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'missed_visit'::text)) AS missed_visit, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'visited_no_bp'::text)) AS visited_no_bp, + count(DISTINCT reporting_patient_states.patient_id) AS patients + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.hypertension = 'yes'::text) AND (reporting_patient_states.months_since_registration = (2)::double precision)) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), monthly_overdue_calls AS ( + SELECT reporting_overdue_calls.appointment_facility_region_id AS region_id, + reporting_overdue_calls.month_date, + count(DISTINCT reporting_overdue_calls.appointment_id) AS call_results + FROM public.reporting_overdue_calls + GROUP BY reporting_overdue_calls.appointment_facility_region_id, reporting_overdue_calls.month_date + ), monthly_follow_ups AS ( + SELECT reporting_patient_follow_ups.facility_id, + reporting_patient_follow_ups.month_date, + count(DISTINCT reporting_patient_follow_ups.patient_id) AS follow_ups + FROM public.reporting_patient_follow_ups + WHERE (reporting_patient_follow_ups.hypertension = 'yes'::text) + GROUP BY reporting_patient_follow_ups.facility_id, reporting_patient_follow_ups.month_date + ), monthly_diabetes_follow_ups AS ( + SELECT reporting_patient_follow_ups.facility_id, + reporting_patient_follow_ups.month_date, + count(DISTINCT reporting_patient_follow_ups.patient_id) AS follow_ups + FROM public.reporting_patient_follow_ups + WHERE (reporting_patient_follow_ups.diabetes = 'yes'::text) + GROUP BY reporting_patient_follow_ups.facility_id, reporting_patient_follow_ups.month_date + ), monthly_hypertension_overdue_patients AS ( + SELECT reporting_overdue_patients.assigned_facility_region_id, + reporting_overdue_patients.month_date, + count(*) FILTER (WHERE (reporting_overdue_patients.is_overdue = 'yes'::text)) AS overdue_patients, + count(*) FILTER (WHERE ((reporting_overdue_patients.is_overdue = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text))) AS contactable_overdue_patients, + count(*) FILTER (WHERE (reporting_overdue_patients.has_called = 'yes'::text)) AS patients_called, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS patients_called_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS patients_called_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS patients_called_with_result_removed_from_list, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text))) AS contactable_patients_called, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS contactable_patients_called_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS contactable_patients_called_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS contactable_patients_called_with_result_removed_from_list, + count(*) FILTER (WHERE (reporting_overdue_patients.has_visited_following_call = 'yes'::text)) AS patients_returned_after_call, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS patients_returned_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS patients_returned_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS patients_returned_with_result_removed_from_list, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text))) AS contactable_patients_returned_after_call, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS contactable_patients_returned_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS contactable_patients_returned_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS contactable_patients_returned_with_result_removed_from_list + FROM public.reporting_overdue_patients + WHERE ((reporting_overdue_patients.hypertension = 'yes'::text) AND (reporting_overdue_patients.under_care = 'yes'::text)) + GROUP BY reporting_overdue_patients.assigned_facility_region_id, reporting_overdue_patients.month_date + ) + SELECT cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string, + rf.facility_id, + rf.facility_name, + rf.facility_type, + rf.facility_size, + rf.facility_region_id, + rf.facility_region_name, + rf.facility_region_slug, + rf.block_region_id, + rf.block_name, + rf.block_slug, + rf.district_id, + rf.district_region_id, + rf.district_name, + rf.district_slug, + rf.state_region_id, + rf.state_name, + rf.state_slug, + rf.organization_id, + rf.organization_region_id, + rf.organization_name, + rf.organization_slug, + registered_patients.cumulative_registrations, + registered_patients.monthly_registrations, + registered_diabetes_patients.cumulative_diabetes_registrations, + registered_diabetes_patients.monthly_diabetes_registrations, + registered_hypertension_and_diabetes_patients.cumulative_hypertension_and_diabetes_registrations, + registered_hypertension_and_diabetes_patients.monthly_hypertension_and_diabetes_registrations, + assigned_patients.under_care, + assigned_patients.lost_to_follow_up, + assigned_patients.dead, + assigned_patients.cumulative_assigned_patients, + assigned_diabetes_patients.diabetes_under_care, + assigned_diabetes_patients.diabetes_lost_to_follow_up, + assigned_diabetes_patients.diabetes_dead, + assigned_diabetes_patients.cumulative_assigned_diabetic_patients, + adjusted_outcomes.controlled_under_care AS adjusted_controlled_under_care, + adjusted_outcomes.uncontrolled_under_care AS adjusted_uncontrolled_under_care, + adjusted_outcomes.missed_visit_under_care AS adjusted_missed_visit_under_care, + adjusted_outcomes.visited_no_bp_under_care AS adjusted_visited_no_bp_under_care, + adjusted_outcomes.missed_visit_lost_to_follow_up AS adjusted_missed_visit_lost_to_follow_up, + adjusted_outcomes.visited_no_bp_lost_to_follow_up AS adjusted_visited_no_bp_lost_to_follow_up, + adjusted_outcomes.patients_under_care AS adjusted_patients_under_care, + adjusted_outcomes.patients_lost_to_follow_up AS adjusted_patients_lost_to_follow_up, + adjusted_diabetes_outcomes.random_bs_below_200_under_care AS adjusted_random_bs_below_200_under_care, + adjusted_diabetes_outcomes.fasting_bs_below_200_under_care AS adjusted_fasting_bs_below_200_under_care, + adjusted_diabetes_outcomes.post_prandial_bs_below_200_under_care AS adjusted_post_prandial_bs_below_200_under_care, + adjusted_diabetes_outcomes.hba1c_bs_below_200_under_care AS adjusted_hba1c_bs_below_200_under_care, + adjusted_diabetes_outcomes.bs_below_200_under_care AS adjusted_bs_below_200_under_care, + adjusted_diabetes_outcomes.random_bs_200_to_300_under_care AS adjusted_random_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.fasting_bs_200_to_300_under_care AS adjusted_fasting_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.post_prandial_bs_200_to_300_under_care AS adjusted_post_prandial_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.hba1c_bs_200_to_300_under_care AS adjusted_hba1c_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.bs_200_to_300_under_care AS adjusted_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.random_bs_over_300_under_care AS adjusted_random_bs_over_300_under_care, + adjusted_diabetes_outcomes.fasting_bs_over_300_under_care AS adjusted_fasting_bs_over_300_under_care, + adjusted_diabetes_outcomes.post_prandial_bs_over_300_under_care AS adjusted_post_prandial_bs_over_300_under_care, + adjusted_diabetes_outcomes.hba1c_bs_over_300_under_care AS adjusted_hba1c_bs_over_300_under_care, + adjusted_diabetes_outcomes.bs_over_300_under_care AS adjusted_bs_over_300_under_care, + adjusted_diabetes_outcomes.bs_missed_visit_under_care AS adjusted_bs_missed_visit_under_care, + adjusted_diabetes_outcomes.visited_no_bs_under_care AS adjusted_visited_no_bs_under_care, + adjusted_diabetes_outcomes.bs_missed_visit_lost_to_follow_up AS adjusted_bs_missed_visit_lost_to_follow_up, + adjusted_diabetes_outcomes.diabetes_patients_under_care AS adjusted_diabetes_patients_under_care, + adjusted_diabetes_outcomes.diabetes_patients_lost_to_follow_up AS adjusted_diabetes_patients_lost_to_follow_up, + monthly_cohort_outcomes.controlled AS monthly_cohort_controlled, + monthly_cohort_outcomes.uncontrolled AS monthly_cohort_uncontrolled, + monthly_cohort_outcomes.missed_visit AS monthly_cohort_missed_visit, + monthly_cohort_outcomes.visited_no_bp AS monthly_cohort_visited_no_bp, + monthly_cohort_outcomes.patients AS monthly_cohort_patients, + monthly_overdue_calls.call_results AS monthly_overdue_calls, + monthly_follow_ups.follow_ups AS monthly_follow_ups, + monthly_diabetes_follow_ups.follow_ups AS monthly_diabetes_follow_ups, + reporting_facility_appointment_scheduled_days.htn_total_appts_scheduled AS total_appts_scheduled, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_0_to_14_days AS appts_scheduled_0_to_14_days, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_15_to_31_days AS appts_scheduled_15_to_31_days, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_32_to_62_days AS appts_scheduled_32_to_62_days, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_more_than_62_days AS appts_scheduled_more_than_62_days, + reporting_facility_appointment_scheduled_days.diabetes_total_appts_scheduled, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_0_to_14_days, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_15_to_31_days, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_32_to_62_days, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_more_than_62_days, + monthly_hypertension_overdue_patients.overdue_patients, + monthly_hypertension_overdue_patients.contactable_overdue_patients, + monthly_hypertension_overdue_patients.patients_called, + monthly_hypertension_overdue_patients.contactable_patients_called, + monthly_hypertension_overdue_patients.patients_called_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.patients_called_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.patients_called_with_result_removed_from_list, + monthly_hypertension_overdue_patients.contactable_patients_called_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.contactable_patients_called_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.contactable_patients_called_with_result_removed_from_list, + monthly_hypertension_overdue_patients.patients_returned_after_call, + monthly_hypertension_overdue_patients.contactable_patients_returned_after_call, + monthly_hypertension_overdue_patients.patients_returned_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.patients_returned_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.patients_returned_with_result_removed_from_list, + monthly_hypertension_overdue_patients.contactable_patients_returned_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.contactable_patients_returned_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.contactable_patients_returned_with_result_removed_from_list + FROM ((((((((((((((public.reporting_facilities rf + JOIN public.reporting_months cal ON (true)) + LEFT JOIN registered_patients ON (((registered_patients.month_date = cal.month_date) AND (registered_patients.region_id = rf.facility_region_id)))) + LEFT JOIN registered_diabetes_patients ON (((registered_diabetes_patients.month_date = cal.month_date) AND (registered_diabetes_patients.region_id = rf.facility_region_id)))) + LEFT JOIN registered_hypertension_and_diabetes_patients ON (((registered_hypertension_and_diabetes_patients.month_date = cal.month_date) AND (registered_hypertension_and_diabetes_patients.region_id = rf.facility_region_id)))) + LEFT JOIN assigned_patients ON (((assigned_patients.month_date = cal.month_date) AND (assigned_patients.region_id = rf.facility_region_id)))) + LEFT JOIN assigned_diabetes_patients ON (((assigned_diabetes_patients.month_date = cal.month_date) AND (assigned_diabetes_patients.region_id = rf.facility_region_id)))) + LEFT JOIN adjusted_outcomes ON (((adjusted_outcomes.month_date = cal.month_date) AND (adjusted_outcomes.region_id = rf.facility_region_id)))) + LEFT JOIN adjusted_diabetes_outcomes ON (((adjusted_diabetes_outcomes.month_date = cal.month_date) AND (adjusted_diabetes_outcomes.region_id = rf.facility_region_id)))) + LEFT JOIN monthly_cohort_outcomes ON (((monthly_cohort_outcomes.month_date = cal.month_date) AND (monthly_cohort_outcomes.region_id = rf.facility_region_id)))) + LEFT JOIN monthly_overdue_calls ON (((monthly_overdue_calls.month_date = cal.month_date) AND (monthly_overdue_calls.region_id = rf.facility_region_id)))) + LEFT JOIN monthly_follow_ups ON (((monthly_follow_ups.month_date = cal.month_date) AND (monthly_follow_ups.facility_id = rf.facility_id)))) + LEFT JOIN monthly_diabetes_follow_ups ON (((monthly_diabetes_follow_ups.month_date = cal.month_date) AND (monthly_diabetes_follow_ups.facility_id = rf.facility_id)))) + LEFT JOIN public.reporting_facility_appointment_scheduled_days ON (((reporting_facility_appointment_scheduled_days.month_date = cal.month_date) AND (reporting_facility_appointment_scheduled_days.facility_id = rf.facility_id)))) + LEFT JOIN monthly_hypertension_overdue_patients ON (((monthly_hypertension_overdue_patients.month_date = cal.month_date) AND (monthly_hypertension_overdue_patients.assigned_facility_region_id = rf.facility_region_id)))) + WITH NO DATA; + SQL + + add_index "reporting_facility_states", ["block_region_id", "month_date"], name: "index_fs_block_month_date" + add_index "reporting_facility_states", ["district_region_id", "month_date"], name: "index_fs_district_month_date" + add_index "reporting_facility_states", ["month_date", "facility_region_id"], name: "index_fs_month_date_region_id", unique: true + add_index "reporting_facility_states", ["organization_region_id", "month_date"], name: "index_fs_organization_month_date" + add_index "reporting_facility_states", ["state_region_id", "month_date"], name: "index_fs_state_month_date" + end + + def down + drop_view :reporting_facility_states, materialized: true + drop_view :reporting_facility_monthly_follow_ups_and_registrations, materialized: true + drop_view :reporting_patient_follow_ups, materialized: true + + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_patient_follow_ups AS + WITH follow_up_blood_pressures AS ( + SELECT DISTINCT ON (p.id, bp.facility_id, bp.user_id, (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)), 'YYYY-MM'::text))) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + bp.id AS visit_id, + 'BloodPressure'::text AS visit_type, + bp.facility_id, + bp.user_id, + bp.recorded_at AS visited_at, + to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)), 'YYYY-MM'::text) AS month_string + FROM (public.patients p + JOIN public.blood_pressures bp ON (((p.id = bp.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at))))))) + WHERE (p.deleted_at IS NULL) + ), follow_up_blood_sugars AS ( + SELECT DISTINCT ON (p.id, bs.facility_id, bs.user_id, (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)), 'YYYY-MM'::text))) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + bs.id AS visit_id, + 'BloodSugar'::text AS visit_type, + bs.facility_id, + bs.user_id, + bs.recorded_at AS visited_at, + to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)), 'YYYY-MM'::text) AS month_string + FROM (public.patients p + JOIN public.blood_sugars bs ON (((p.id = bs.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at))))))) + WHERE (p.deleted_at IS NULL) + ), follow_up_prescription_drugs AS ( + SELECT DISTINCT ON (p.id, pd.facility_id, pd.user_id, (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, pd.device_created_at)), 'YYYY-MM'::text))) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + pd.id AS visit_id, + 'PrescriptionDrug'::text AS visit_type, + pd.facility_id, + pd.user_id, + pd.device_created_at AS visited_at, + to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, pd.device_created_at)), 'YYYY-MM'::text) AS month_string + FROM (public.patients p + JOIN public.prescription_drugs pd ON (((p.id = pd.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, pd.device_created_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at))))))) + WHERE (p.deleted_at IS NULL) + ), follow_up_appointments AS ( + SELECT DISTINCT ON (p.id, app.creation_facility_id, app.user_id, (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, app.device_created_at)), 'YYYY-MM'::text))) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + app.id AS visit_id, + 'Appointment'::text AS visit_type, + app.creation_facility_id AS facility_id, + app.user_id, + app.device_created_at AS visited_at, + to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, app.device_created_at)), 'YYYY-MM'::text) AS month_string + FROM (public.patients p + JOIN public.appointments app ON (((p.id = app.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, app.device_created_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at))))))) + WHERE (p.deleted_at IS NULL) + ), all_follow_ups AS ( + SELECT follow_up_blood_pressures.patient_id, + follow_up_blood_pressures.patient_gender, + follow_up_blood_pressures.visit_id, + follow_up_blood_pressures.visit_type, + follow_up_blood_pressures.facility_id, + follow_up_blood_pressures.user_id, + follow_up_blood_pressures.visited_at, + follow_up_blood_pressures.month_string + FROM follow_up_blood_pressures + UNION + SELECT follow_up_blood_sugars.patient_id, + follow_up_blood_sugars.patient_gender, + follow_up_blood_sugars.visit_id, + follow_up_blood_sugars.visit_type, + follow_up_blood_sugars.facility_id, + follow_up_blood_sugars.user_id, + follow_up_blood_sugars.visited_at, + follow_up_blood_sugars.month_string + FROM follow_up_blood_sugars + UNION + SELECT follow_up_prescription_drugs.patient_id, + follow_up_prescription_drugs.patient_gender, + follow_up_prescription_drugs.visit_id, + follow_up_prescription_drugs.visit_type, + follow_up_prescription_drugs.facility_id, + follow_up_prescription_drugs.user_id, + follow_up_prescription_drugs.visited_at, + follow_up_prescription_drugs.month_string + FROM follow_up_prescription_drugs + UNION + SELECT follow_up_appointments.patient_id, + follow_up_appointments.patient_gender, + follow_up_appointments.visit_id, + follow_up_appointments.visit_type, + follow_up_appointments.facility_id, + follow_up_appointments.user_id, + follow_up_appointments.visited_at, + follow_up_appointments.month_string + FROM follow_up_appointments + ) + SELECT DISTINCT ON (cal.month_string, all_follow_ups.facility_id, all_follow_ups.user_id, all_follow_ups.patient_id) all_follow_ups.patient_id, + all_follow_ups.patient_gender, + all_follow_ups.facility_id, + mh.diabetes, + mh.hypertension, + all_follow_ups.user_id, + all_follow_ups.visit_id, + all_follow_ups.visit_type, + all_follow_ups.visited_at, + cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string + FROM ((all_follow_ups + JOIN public.medical_histories mh ON ((all_follow_ups.patient_id = mh.patient_id))) + LEFT JOIN public.reporting_months cal ON ((all_follow_ups.month_string = cal.month_string))) + ORDER BY cal.month_string DESC + WITH NO DATA; + SQL + + add_index :reporting_patient_follow_ups, [:facility_id], name: "index_reporting_patient_follow_ups_on_facility_id" + add_index :reporting_patient_follow_ups, [:patient_id, :user_id, :facility_id, :month_date], unique: true, name: "reporting_patient_follow_ups_unique_index" + + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_facility_monthly_follow_ups_and_registrations AS + WITH monthly_registration_patient_states AS ( + SELECT reporting_patient_states.registration_facility_id AS facility_id, + reporting_patient_states.month_date, + reporting_patient_states.gender, + reporting_patient_states.hypertension, + reporting_patient_states.diabetes + FROM public.reporting_patient_states + WHERE (reporting_patient_states.months_since_registration = (0)::double precision) + ), registered_patients AS ( + SELECT monthly_registration_patient_states.facility_id, + monthly_registration_patient_states.month_date, + count(*) AS monthly_registrations_all, + count(*) FILTER (WHERE (monthly_registration_patient_states.hypertension = 'yes'::text)) AS monthly_registrations_htn_all, + count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'female'::text))) AS monthly_registrations_htn_female, + count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'male'::text))) AS monthly_registrations_htn_male, + count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'transgender'::text))) AS monthly_registrations_htn_transgender, + count(*) FILTER (WHERE (monthly_registration_patient_states.diabetes = 'yes'::text)) AS monthly_registrations_dm_all, + count(*) FILTER (WHERE ((monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'female'::text))) AS monthly_registrations_dm_female, + count(*) FILTER (WHERE ((monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'male'::text))) AS monthly_registrations_dm_male, + count(*) FILTER (WHERE ((monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'transgender'::text))) AS monthly_registrations_dm_transgender, + count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) OR (monthly_registration_patient_states.diabetes = 'yes'::text))) AS monthly_registrations_htn_or_dm, + count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text))) AS monthly_registrations_htn_and_dm, + count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'female'::text))) AS monthly_registrations_htn_and_dm_female, + count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'male'::text))) AS monthly_registrations_htn_and_dm_male, + count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'transgender'::text))) AS monthly_registrations_htn_and_dm_transgender, + (count(*) FILTER (WHERE (monthly_registration_patient_states.hypertension = 'yes'::text)) - count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text)))) AS monthly_registrations_htn_only, + (count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'female'::text))) - count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'female'::text)))) AS monthly_registrations_htn_only_female, + (count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'male'::text))) - count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'male'::text)))) AS monthly_registrations_htn_only_male, + (count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'transgender'::text))) - count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'transgender'::text)))) AS monthly_registrations_htn_only_transgender, + (count(*) FILTER (WHERE (monthly_registration_patient_states.diabetes = 'yes'::text)) - count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text)))) AS monthly_registrations_dm_only, + (count(*) FILTER (WHERE ((monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'female'::text))) - count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'female'::text)))) AS monthly_registrations_dm_only_female, + (count(*) FILTER (WHERE ((monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'male'::text))) - count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'male'::text)))) AS monthly_registrations_dm_only_male, + (count(*) FILTER (WHERE ((monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'transgender'::text))) - count(*) FILTER (WHERE ((monthly_registration_patient_states.hypertension = 'yes'::text) AND (monthly_registration_patient_states.diabetes = 'yes'::text) AND ((monthly_registration_patient_states.gender)::text = 'transgender'::text)))) AS monthly_registrations_dm_only_transgender + FROM monthly_registration_patient_states + GROUP BY monthly_registration_patient_states.facility_id, monthly_registration_patient_states.month_date + ), follow_ups AS ( + SELECT reporting_patient_follow_ups.facility_id, + reporting_patient_follow_ups.month_date, + count(DISTINCT reporting_patient_follow_ups.patient_id) AS monthly_follow_ups_all, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE (reporting_patient_follow_ups.hypertension = 'yes'::text)) AS monthly_follow_ups_htn_all, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'female'::public.gender_enum))) AS monthly_follow_ups_htn_female, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'male'::public.gender_enum))) AS monthly_follow_ups_htn_male, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'transgender'::public.gender_enum))) AS monthly_follow_ups_htn_transgender, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE (reporting_patient_follow_ups.diabetes = 'yes'::text)) AS monthly_follow_ups_dm_all, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'female'::public.gender_enum))) AS monthly_follow_ups_dm_female, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'male'::public.gender_enum))) AS monthly_follow_ups_dm_male, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'transgender'::public.gender_enum))) AS monthly_follow_ups_dm_transgender, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) OR (reporting_patient_follow_ups.diabetes = 'yes'::text))) AS monthly_follow_ups_htn_or_dm, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text))) AS monthly_follow_ups_htn_and_dm, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'female'::public.gender_enum))) AS monthly_follow_ups_htn_and_dm_female, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'male'::public.gender_enum))) AS monthly_follow_ups_htn_and_dm_male, + count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'transgender'::public.gender_enum))) AS monthly_follow_ups_htn_and_dm_transgender, + (count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE (reporting_patient_follow_ups.hypertension = 'yes'::text)) - count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text)))) AS monthly_follow_ups_htn_only, + (count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'female'::public.gender_enum))) - count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'female'::public.gender_enum)))) AS monthly_follow_ups_htn_only_female, + (count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'male'::public.gender_enum))) - count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'male'::public.gender_enum)))) AS monthly_follow_ups_htn_only_male, + (count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'transgender'::public.gender_enum))) - count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'transgender'::public.gender_enum)))) AS monthly_follow_ups_htn_only_transgender, + (count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE (reporting_patient_follow_ups.diabetes = 'yes'::text)) - count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text)))) AS monthly_follow_ups_dm_only, + (count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'female'::public.gender_enum))) - count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'female'::public.gender_enum)))) AS monthly_follow_ups_dm_only_female, + (count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'male'::public.gender_enum))) - count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'male'::public.gender_enum)))) AS monthly_follow_ups_dm_only_male, + (count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'transgender'::public.gender_enum))) - count(DISTINCT reporting_patient_follow_ups.patient_id) FILTER (WHERE ((reporting_patient_follow_ups.hypertension = 'yes'::text) AND (reporting_patient_follow_ups.diabetes = 'yes'::text) AND (reporting_patient_follow_ups.patient_gender = 'transgender'::public.gender_enum)))) AS monthly_follow_ups_dm_only_transgender + FROM public.reporting_patient_follow_ups + GROUP BY reporting_patient_follow_ups.facility_id, reporting_patient_follow_ups.month_date + ) + SELECT rf.facility_region_slug, + rf.facility_id, + rf.facility_region_id, + rf.block_region_id, + rf.district_region_id, + rf.state_region_id, + cal.month_date, + COALESCE(registered_patients.monthly_registrations_all, (0)::bigint) AS monthly_registrations_all, + COALESCE(registered_patients.monthly_registrations_htn_all, (0)::bigint) AS monthly_registrations_htn_all, + COALESCE(registered_patients.monthly_registrations_htn_male, (0)::bigint) AS monthly_registrations_htn_male, + COALESCE(registered_patients.monthly_registrations_htn_female, (0)::bigint) AS monthly_registrations_htn_female, + COALESCE(registered_patients.monthly_registrations_htn_transgender, (0)::bigint) AS monthly_registrations_htn_transgender, + COALESCE(registered_patients.monthly_registrations_dm_all, (0)::bigint) AS monthly_registrations_dm_all, + COALESCE(registered_patients.monthly_registrations_dm_male, (0)::bigint) AS monthly_registrations_dm_male, + COALESCE(registered_patients.monthly_registrations_dm_female, (0)::bigint) AS monthly_registrations_dm_female, + COALESCE(registered_patients.monthly_registrations_dm_transgender, (0)::bigint) AS monthly_registrations_dm_transgender, + COALESCE(follow_ups.monthly_follow_ups_all, (0)::bigint) AS monthly_follow_ups_all, + COALESCE(follow_ups.monthly_follow_ups_htn_all, (0)::bigint) AS monthly_follow_ups_htn_all, + COALESCE(follow_ups.monthly_follow_ups_htn_female, (0)::bigint) AS monthly_follow_ups_htn_female, + COALESCE(follow_ups.monthly_follow_ups_htn_male, (0)::bigint) AS monthly_follow_ups_htn_male, + COALESCE(follow_ups.monthly_follow_ups_htn_transgender, (0)::bigint) AS monthly_follow_ups_htn_transgender, + COALESCE(follow_ups.monthly_follow_ups_dm_all, (0)::bigint) AS monthly_follow_ups_dm_all, + COALESCE(follow_ups.monthly_follow_ups_dm_female, (0)::bigint) AS monthly_follow_ups_dm_female, + COALESCE(follow_ups.monthly_follow_ups_dm_male, (0)::bigint) AS monthly_follow_ups_dm_male, + COALESCE(follow_ups.monthly_follow_ups_dm_transgender, (0)::bigint) AS monthly_follow_ups_dm_transgender, + COALESCE(registered_patients.monthly_registrations_htn_or_dm, (0)::bigint) AS monthly_registrations_htn_or_dm, + COALESCE(registered_patients.monthly_registrations_htn_only, (0)::bigint) AS monthly_registrations_htn_only, + COALESCE(registered_patients.monthly_registrations_htn_only_male, (0)::bigint) AS monthly_registrations_htn_only_male, + COALESCE(registered_patients.monthly_registrations_htn_only_female, (0)::bigint) AS monthly_registrations_htn_only_female, + COALESCE(registered_patients.monthly_registrations_htn_only_transgender, (0)::bigint) AS monthly_registrations_htn_only_transgender, + COALESCE(registered_patients.monthly_registrations_dm_only, (0)::bigint) AS monthly_registrations_dm_only, + COALESCE(registered_patients.monthly_registrations_dm_only_male, (0)::bigint) AS monthly_registrations_dm_only_male, + COALESCE(registered_patients.monthly_registrations_dm_only_female, (0)::bigint) AS monthly_registrations_dm_only_female, + COALESCE(registered_patients.monthly_registrations_dm_only_transgender, (0)::bigint) AS monthly_registrations_dm_only_transgender, + COALESCE(registered_patients.monthly_registrations_htn_and_dm, (0)::bigint) AS monthly_registrations_htn_and_dm, + COALESCE(registered_patients.monthly_registrations_htn_and_dm_male, (0)::bigint) AS monthly_registrations_htn_and_dm_male, + COALESCE(registered_patients.monthly_registrations_htn_and_dm_female, (0)::bigint) AS monthly_registrations_htn_and_dm_female, + COALESCE(registered_patients.monthly_registrations_htn_and_dm_transgender, (0)::bigint) AS monthly_registrations_htn_and_dm_transgender, + COALESCE(follow_ups.monthly_follow_ups_htn_or_dm, (0)::bigint) AS monthly_follow_ups_htn_or_dm, + COALESCE(follow_ups.monthly_follow_ups_htn_only, (0)::bigint) AS monthly_follow_ups_htn_only, + COALESCE(follow_ups.monthly_follow_ups_htn_only_female, (0)::bigint) AS monthly_follow_ups_htn_only_female, + COALESCE(follow_ups.monthly_follow_ups_htn_only_male, (0)::bigint) AS monthly_follow_ups_htn_only_male, + COALESCE(follow_ups.monthly_follow_ups_htn_only_transgender, (0)::bigint) AS monthly_follow_ups_htn_only_transgender, + COALESCE(follow_ups.monthly_follow_ups_dm_only, (0)::bigint) AS monthly_follow_ups_dm_only, + COALESCE(follow_ups.monthly_follow_ups_dm_only_female, (0)::bigint) AS monthly_follow_ups_dm_only_female, + COALESCE(follow_ups.monthly_follow_ups_dm_only_male, (0)::bigint) AS monthly_follow_ups_dm_only_male, + COALESCE(follow_ups.monthly_follow_ups_dm_only_transgender, (0)::bigint) AS monthly_follow_ups_dm_only_transgender, + COALESCE(follow_ups.monthly_follow_ups_htn_and_dm, (0)::bigint) AS monthly_follow_ups_htn_and_dm, + COALESCE(follow_ups.monthly_follow_ups_htn_and_dm_male, (0)::bigint) AS monthly_follow_ups_htn_and_dm_male, + COALESCE(follow_ups.monthly_follow_ups_htn_and_dm_female, (0)::bigint) AS monthly_follow_ups_htn_and_dm_female, + COALESCE(follow_ups.monthly_follow_ups_htn_and_dm_transgender, (0)::bigint) AS monthly_follow_ups_htn_and_dm_transgender + FROM (((public.reporting_facilities rf + JOIN public.reporting_months cal ON (true)) + LEFT JOIN registered_patients ON (((registered_patients.month_date = cal.month_date) AND (registered_patients.facility_id = rf.facility_id)))) + LEFT JOIN follow_ups ON (((follow_ups.month_date = cal.month_date) AND (follow_ups.facility_id = rf.facility_id)))) + ORDER BY cal.month_date DESC + WITH NO DATA; + SQL + + add_index :reporting_facility_monthly_follow_ups_and_registrations, [:facility_id], name: "facility_monthly_fr_facility_id" + add_index :reporting_facility_monthly_follow_ups_and_registrations, [:facility_region_id], name: "facility_monthly_fr_facility_region_id" + add_index :reporting_facility_monthly_follow_ups_and_registrations, [:month_date, :facility_region_id], unique: true, name: "facility_monthly_fr_month_date_facility_region_id" + + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_facility_states AS + WITH registered_patients AS ( + SELECT reporting_patient_states.registration_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(*) AS cumulative_registrations, + count(*) FILTER (WHERE (reporting_patient_states.months_since_registration = (0)::double precision)) AS monthly_registrations + FROM public.reporting_patient_states + WHERE (reporting_patient_states.hypertension = 'yes'::text) + GROUP BY reporting_patient_states.registration_facility_region_id, reporting_patient_states.month_date + ), registered_diabetes_patients AS ( + SELECT reporting_patient_states.registration_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(*) AS cumulative_diabetes_registrations, + count(*) FILTER (WHERE (reporting_patient_states.months_since_registration = (0)::double precision)) AS monthly_diabetes_registrations + FROM public.reporting_patient_states + WHERE (reporting_patient_states.diabetes = 'yes'::text) + GROUP BY reporting_patient_states.registration_facility_region_id, reporting_patient_states.month_date + ), registered_hypertension_and_diabetes_patients AS ( + SELECT reporting_patient_states.registration_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(*) AS cumulative_hypertension_and_diabetes_registrations, + count(*) FILTER (WHERE (reporting_patient_states.months_since_registration = (0)::double precision)) AS monthly_hypertension_and_diabetes_registrations + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.hypertension = 'yes'::text) AND (reporting_patient_states.diabetes = 'yes'::text)) + GROUP BY reporting_patient_states.registration_facility_region_id, reporting_patient_states.month_date + ), assigned_patients AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'dead'::text)) AS dead, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state <> 'dead'::text)) AS cumulative_assigned_patients + FROM public.reporting_patient_states + WHERE (reporting_patient_states.hypertension = 'yes'::text) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), assigned_diabetes_patients AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS diabetes_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS diabetes_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'dead'::text)) AS diabetes_dead, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state <> 'dead'::text)) AS cumulative_assigned_diabetic_patients + FROM public.reporting_patient_states + WHERE (reporting_patient_states.diabetes = 'yes'::text) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), adjusted_outcomes AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'controlled'::text))) AS controlled_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'uncontrolled'::text))) AS uncontrolled_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS missed_visit_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'visited_no_bp'::text))) AS visited_no_bp_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS missed_visit_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'visited_no_bp'::text))) AS visited_no_bp_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS patients_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS patients_lost_to_follow_up + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.hypertension = 'yes'::text) AND (reporting_patient_states.months_since_registration >= (3)::double precision)) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), adjusted_diabetes_outcomes AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'random'::text))) AS random_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'post_prandial'::text))) AS post_prandial_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'fasting'::text))) AS fasting_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'hba1c'::text))) AS hba1c_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text))) AS bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'random'::text))) AS random_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'post_prandial'::text))) AS post_prandial_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'fasting'::text))) AS fasting_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'hba1c'::text))) AS hba1c_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text))) AS bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'random'::text))) AS random_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'post_prandial'::text))) AS post_prandial_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'fasting'::text))) AS fasting_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'hba1c'::text))) AS hba1c_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text))) AS bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS bs_missed_visit_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'visited_no_bs'::text))) AS visited_no_bs_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS bs_missed_visit_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS diabetes_patients_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS diabetes_patients_lost_to_follow_up + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.diabetes = 'yes'::text) AND (reporting_patient_states.months_since_registration >= (3)::double precision)) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), monthly_cohort_outcomes AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'controlled'::text)) AS controlled, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'uncontrolled'::text)) AS uncontrolled, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'missed_visit'::text)) AS missed_visit, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'visited_no_bp'::text)) AS visited_no_bp, + count(DISTINCT reporting_patient_states.patient_id) AS patients + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.hypertension = 'yes'::text) AND (reporting_patient_states.months_since_registration = (2)::double precision)) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), monthly_overdue_calls AS ( + SELECT reporting_overdue_calls.appointment_facility_region_id AS region_id, + reporting_overdue_calls.month_date, + count(DISTINCT reporting_overdue_calls.appointment_id) AS call_results + FROM public.reporting_overdue_calls + GROUP BY reporting_overdue_calls.appointment_facility_region_id, reporting_overdue_calls.month_date + ), monthly_follow_ups AS ( + SELECT reporting_patient_follow_ups.facility_id, + reporting_patient_follow_ups.month_date, + count(DISTINCT reporting_patient_follow_ups.patient_id) AS follow_ups + FROM public.reporting_patient_follow_ups + WHERE (reporting_patient_follow_ups.hypertension = 'yes'::text) + GROUP BY reporting_patient_follow_ups.facility_id, reporting_patient_follow_ups.month_date + ), monthly_diabetes_follow_ups AS ( + SELECT reporting_patient_follow_ups.facility_id, + reporting_patient_follow_ups.month_date, + count(DISTINCT reporting_patient_follow_ups.patient_id) AS follow_ups + FROM public.reporting_patient_follow_ups + WHERE (reporting_patient_follow_ups.diabetes = 'yes'::text) + GROUP BY reporting_patient_follow_ups.facility_id, reporting_patient_follow_ups.month_date + ), monthly_hypertension_overdue_patients AS ( + SELECT reporting_overdue_patients.assigned_facility_region_id, + reporting_overdue_patients.month_date, + count(*) FILTER (WHERE (reporting_overdue_patients.is_overdue = 'yes'::text)) AS overdue_patients, + count(*) FILTER (WHERE ((reporting_overdue_patients.is_overdue = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text))) AS contactable_overdue_patients, + count(*) FILTER (WHERE (reporting_overdue_patients.has_called = 'yes'::text)) AS patients_called, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS patients_called_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS patients_called_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS patients_called_with_result_removed_from_list, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text))) AS contactable_patients_called, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS contactable_patients_called_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS contactable_patients_called_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS contactable_patients_called_with_result_removed_from_list, + count(*) FILTER (WHERE (reporting_overdue_patients.has_visited_following_call = 'yes'::text)) AS patients_returned_after_call, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS patients_returned_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS patients_returned_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS patients_returned_with_result_removed_from_list, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text))) AS contactable_patients_returned_after_call, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS contactable_patients_returned_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS contactable_patients_returned_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS contactable_patients_returned_with_result_removed_from_list + FROM public.reporting_overdue_patients + WHERE ((reporting_overdue_patients.hypertension = 'yes'::text) AND (reporting_overdue_patients.under_care = 'yes'::text)) + GROUP BY reporting_overdue_patients.assigned_facility_region_id, reporting_overdue_patients.month_date + ) + SELECT cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string, + rf.facility_id, + rf.facility_name, + rf.facility_type, + rf.facility_size, + rf.facility_region_id, + rf.facility_region_name, + rf.facility_region_slug, + rf.block_region_id, + rf.block_name, + rf.block_slug, + rf.district_id, + rf.district_region_id, + rf.district_name, + rf.district_slug, + rf.state_region_id, + rf.state_name, + rf.state_slug, + rf.organization_id, + rf.organization_region_id, + rf.organization_name, + rf.organization_slug, + registered_patients.cumulative_registrations, + registered_patients.monthly_registrations, + registered_diabetes_patients.cumulative_diabetes_registrations, + registered_diabetes_patients.monthly_diabetes_registrations, + registered_hypertension_and_diabetes_patients.cumulative_hypertension_and_diabetes_registrations, + registered_hypertension_and_diabetes_patients.monthly_hypertension_and_diabetes_registrations, + assigned_patients.under_care, + assigned_patients.lost_to_follow_up, + assigned_patients.dead, + assigned_patients.cumulative_assigned_patients, + assigned_diabetes_patients.diabetes_under_care, + assigned_diabetes_patients.diabetes_lost_to_follow_up, + assigned_diabetes_patients.diabetes_dead, + assigned_diabetes_patients.cumulative_assigned_diabetic_patients, + adjusted_outcomes.controlled_under_care AS adjusted_controlled_under_care, + adjusted_outcomes.uncontrolled_under_care AS adjusted_uncontrolled_under_care, + adjusted_outcomes.missed_visit_under_care AS adjusted_missed_visit_under_care, + adjusted_outcomes.visited_no_bp_under_care AS adjusted_visited_no_bp_under_care, + adjusted_outcomes.missed_visit_lost_to_follow_up AS adjusted_missed_visit_lost_to_follow_up, + adjusted_outcomes.visited_no_bp_lost_to_follow_up AS adjusted_visited_no_bp_lost_to_follow_up, + adjusted_outcomes.patients_under_care AS adjusted_patients_under_care, + adjusted_outcomes.patients_lost_to_follow_up AS adjusted_patients_lost_to_follow_up, + adjusted_diabetes_outcomes.random_bs_below_200_under_care AS adjusted_random_bs_below_200_under_care, + adjusted_diabetes_outcomes.fasting_bs_below_200_under_care AS adjusted_fasting_bs_below_200_under_care, + adjusted_diabetes_outcomes.post_prandial_bs_below_200_under_care AS adjusted_post_prandial_bs_below_200_under_care, + adjusted_diabetes_outcomes.hba1c_bs_below_200_under_care AS adjusted_hba1c_bs_below_200_under_care, + adjusted_diabetes_outcomes.bs_below_200_under_care AS adjusted_bs_below_200_under_care, + adjusted_diabetes_outcomes.random_bs_200_to_300_under_care AS adjusted_random_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.fasting_bs_200_to_300_under_care AS adjusted_fasting_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.post_prandial_bs_200_to_300_under_care AS adjusted_post_prandial_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.hba1c_bs_200_to_300_under_care AS adjusted_hba1c_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.bs_200_to_300_under_care AS adjusted_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.random_bs_over_300_under_care AS adjusted_random_bs_over_300_under_care, + adjusted_diabetes_outcomes.fasting_bs_over_300_under_care AS adjusted_fasting_bs_over_300_under_care, + adjusted_diabetes_outcomes.post_prandial_bs_over_300_under_care AS adjusted_post_prandial_bs_over_300_under_care, + adjusted_diabetes_outcomes.hba1c_bs_over_300_under_care AS adjusted_hba1c_bs_over_300_under_care, + adjusted_diabetes_outcomes.bs_over_300_under_care AS adjusted_bs_over_300_under_care, + adjusted_diabetes_outcomes.bs_missed_visit_under_care AS adjusted_bs_missed_visit_under_care, + adjusted_diabetes_outcomes.visited_no_bs_under_care AS adjusted_visited_no_bs_under_care, + adjusted_diabetes_outcomes.bs_missed_visit_lost_to_follow_up AS adjusted_bs_missed_visit_lost_to_follow_up, + adjusted_diabetes_outcomes.diabetes_patients_under_care AS adjusted_diabetes_patients_under_care, + adjusted_diabetes_outcomes.diabetes_patients_lost_to_follow_up AS adjusted_diabetes_patients_lost_to_follow_up, + monthly_cohort_outcomes.controlled AS monthly_cohort_controlled, + monthly_cohort_outcomes.uncontrolled AS monthly_cohort_uncontrolled, + monthly_cohort_outcomes.missed_visit AS monthly_cohort_missed_visit, + monthly_cohort_outcomes.visited_no_bp AS monthly_cohort_visited_no_bp, + monthly_cohort_outcomes.patients AS monthly_cohort_patients, + monthly_overdue_calls.call_results AS monthly_overdue_calls, + monthly_follow_ups.follow_ups AS monthly_follow_ups, + monthly_diabetes_follow_ups.follow_ups AS monthly_diabetes_follow_ups, + reporting_facility_appointment_scheduled_days.htn_total_appts_scheduled AS total_appts_scheduled, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_0_to_14_days AS appts_scheduled_0_to_14_days, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_15_to_31_days AS appts_scheduled_15_to_31_days, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_32_to_62_days AS appts_scheduled_32_to_62_days, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_more_than_62_days AS appts_scheduled_more_than_62_days, + reporting_facility_appointment_scheduled_days.diabetes_total_appts_scheduled, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_0_to_14_days, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_15_to_31_days, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_32_to_62_days, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_more_than_62_days, + monthly_hypertension_overdue_patients.overdue_patients, + monthly_hypertension_overdue_patients.contactable_overdue_patients, + monthly_hypertension_overdue_patients.patients_called, + monthly_hypertension_overdue_patients.contactable_patients_called, + monthly_hypertension_overdue_patients.patients_called_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.patients_called_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.patients_called_with_result_removed_from_list, + monthly_hypertension_overdue_patients.contactable_patients_called_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.contactable_patients_called_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.contactable_patients_called_with_result_removed_from_list, + monthly_hypertension_overdue_patients.patients_returned_after_call, + monthly_hypertension_overdue_patients.contactable_patients_returned_after_call, + monthly_hypertension_overdue_patients.patients_returned_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.patients_returned_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.patients_returned_with_result_removed_from_list, + monthly_hypertension_overdue_patients.contactable_patients_returned_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.contactable_patients_returned_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.contactable_patients_returned_with_result_removed_from_list + FROM ((((((((((((((public.reporting_facilities rf + JOIN public.reporting_months cal ON (true)) + LEFT JOIN registered_patients ON (((registered_patients.month_date = cal.month_date) AND (registered_patients.region_id = rf.facility_region_id)))) + LEFT JOIN registered_diabetes_patients ON (((registered_diabetes_patients.month_date = cal.month_date) AND (registered_diabetes_patients.region_id = rf.facility_region_id)))) + LEFT JOIN registered_hypertension_and_diabetes_patients ON (((registered_hypertension_and_diabetes_patients.month_date = cal.month_date) AND (registered_hypertension_and_diabetes_patients.region_id = rf.facility_region_id)))) + LEFT JOIN assigned_patients ON (((assigned_patients.month_date = cal.month_date) AND (assigned_patients.region_id = rf.facility_region_id)))) + LEFT JOIN assigned_diabetes_patients ON (((assigned_diabetes_patients.month_date = cal.month_date) AND (assigned_diabetes_patients.region_id = rf.facility_region_id)))) + LEFT JOIN adjusted_outcomes ON (((adjusted_outcomes.month_date = cal.month_date) AND (adjusted_outcomes.region_id = rf.facility_region_id)))) + LEFT JOIN adjusted_diabetes_outcomes ON (((adjusted_diabetes_outcomes.month_date = cal.month_date) AND (adjusted_diabetes_outcomes.region_id = rf.facility_region_id)))) + LEFT JOIN monthly_cohort_outcomes ON (((monthly_cohort_outcomes.month_date = cal.month_date) AND (monthly_cohort_outcomes.region_id = rf.facility_region_id)))) + LEFT JOIN monthly_overdue_calls ON (((monthly_overdue_calls.month_date = cal.month_date) AND (monthly_overdue_calls.region_id = rf.facility_region_id)))) + LEFT JOIN monthly_follow_ups ON (((monthly_follow_ups.month_date = cal.month_date) AND (monthly_follow_ups.facility_id = rf.facility_id)))) + LEFT JOIN monthly_diabetes_follow_ups ON (((monthly_diabetes_follow_ups.month_date = cal.month_date) AND (monthly_diabetes_follow_ups.facility_id = rf.facility_id)))) + LEFT JOIN public.reporting_facility_appointment_scheduled_days ON (((reporting_facility_appointment_scheduled_days.month_date = cal.month_date) AND (reporting_facility_appointment_scheduled_days.facility_id = rf.facility_id)))) + LEFT JOIN monthly_hypertension_overdue_patients ON (((monthly_hypertension_overdue_patients.month_date = cal.month_date) AND (monthly_hypertension_overdue_patients.assigned_facility_region_id = rf.facility_region_id)))) + WITH NO DATA; + SQL + + add_index "reporting_facility_states", ["block_region_id", "month_date"], name: "index_fs_block_month_date" + add_index "reporting_facility_states", ["district_region_id", "month_date"], name: "index_fs_district_month_date" + add_index "reporting_facility_states", ["month_date", "facility_region_id"], name: "index_fs_month_date_region_id", unique: true + add_index "reporting_facility_states", ["organization_region_id", "month_date"], name: "index_fs_organization_month_date" + add_index "reporting_facility_states", ["state_region_id", "month_date"], name: "index_fs_state_month_date" + end +end diff --git a/db/migrate/20251208104102_update_reporting_facility_appointment_scheduled_days_to_version5.rb b/db/migrate/20251208104102_update_reporting_facility_appointment_scheduled_days_to_version5.rb new file mode 100644 index 0000000000..8e538411a6 --- /dev/null +++ b/db/migrate/20251208104102_update_reporting_facility_appointment_scheduled_days_to_version5.rb @@ -0,0 +1,670 @@ +class UpdateReportingFacilityAppointmentScheduledDaysToVersion5 < ActiveRecord::Migration[6.1] + def up + drop_view :reporting_facility_states, materialized: true + drop_view :reporting_facility_appointment_scheduled_days, materialized: true + + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_facility_appointment_scheduled_days AS + WITH latest_medical_histories AS ( + SELECT DISTINCT ON (patient_id) mh.* + FROM medical_histories mh + WHERE mh.deleted_at IS NULL + ORDER BY patient_id, mh.device_created_at DESC + ), + latest_appointments_per_patient_per_month AS ( + SELECT DISTINCT ON (patient_id, month_date) a.*, + lmh.hypertension, + lmh.diabetes, + to_char(a.device_created_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')), 'YYYY-MM-01')::date month_date + FROM appointments a + INNER JOIN patients p ON p.id = a.patient_id + INNER JOIN latest_medical_histories lmh ON lmh.patient_id = a.patient_id + WHERE a.scheduled_date >= date_trunc('day', a.device_created_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE'))) + AND a.device_created_at >= date_trunc('month', (now() AT TIME ZONE 'UTC') - INTERVAL '6 months') + AND date_trunc('month', a.device_created_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE'))) + > date_trunc('month', p.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE'))) + AND p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL + AND a.deleted_at IS NULL + AND (lmh.hypertension = 'yes' OR lmh.diabetes = 'yes') + ORDER BY a.patient_id, month_date, a.device_created_at desc + ), + scheduled_days_distribution AS ( + SELECT month_date, + width_bucket( + extract('days' FROM (scheduled_date - date_trunc('day', device_created_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))))::integer, + array[0, 15, 32, 63] + ) bucket, + COUNT(*) number_of_appointments, + hypertension, + diabetes, + creation_facility_id facility_id + FROM latest_appointments_per_patient_per_month + GROUP BY bucket, creation_facility_id, month_date, hypertension, diabetes + ) + SELECT facility_id, + month_date, + (SUM(number_of_appointments) FILTER (WHERE bucket = 1 AND hypertension = 'yes'))::integer AS htn_appts_scheduled_0_to_14_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 2 AND hypertension = 'yes'))::integer AS htn_appts_scheduled_15_to_31_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 3 AND hypertension = 'yes'))::integer AS htn_appts_scheduled_32_to_62_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 4 AND hypertension = 'yes'))::integer AS htn_appts_scheduled_more_than_62_days, + (SUM(number_of_appointments) FILTER (WHERE hypertension = 'yes'))::integer htn_total_appts_scheduled, + + (SUM(number_of_appointments) FILTER (WHERE bucket = 1 AND diabetes = 'yes'))::integer AS diabetes_appts_scheduled_0_to_14_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 2 AND diabetes = 'yes'))::integer AS diabetes_appts_scheduled_15_to_31_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 3 AND diabetes = 'yes'))::integer AS diabetes_appts_scheduled_32_to_62_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 4 AND diabetes = 'yes'))::integer AS diabetes_appts_scheduled_more_than_62_days, + (SUM(number_of_appointments) FILTER (WHERE diabetes = 'yes'))::integer diabetes_total_appts_scheduled + FROM scheduled_days_distribution + GROUP BY facility_id, month_date + WITH NO DATA; + + SQL + + add_index :reporting_facility_appointment_scheduled_days, [:month_date, :facility_id], name: "index_reporting_facility_appointment_scheduled_days", unique: true + + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_facility_states AS + WITH registered_patients AS ( + SELECT reporting_patient_states.registration_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(*) AS cumulative_registrations, + count(*) FILTER (WHERE (reporting_patient_states.months_since_registration = (0)::double precision)) AS monthly_registrations + FROM public.reporting_patient_states + WHERE (reporting_patient_states.hypertension = 'yes'::text) + GROUP BY reporting_patient_states.registration_facility_region_id, reporting_patient_states.month_date + ), registered_diabetes_patients AS ( + SELECT reporting_patient_states.registration_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(*) AS cumulative_diabetes_registrations, + count(*) FILTER (WHERE (reporting_patient_states.months_since_registration = (0)::double precision)) AS monthly_diabetes_registrations + FROM public.reporting_patient_states + WHERE (reporting_patient_states.diabetes = 'yes'::text) + GROUP BY reporting_patient_states.registration_facility_region_id, reporting_patient_states.month_date + ), registered_hypertension_and_diabetes_patients AS ( + SELECT reporting_patient_states.registration_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(*) AS cumulative_hypertension_and_diabetes_registrations, + count(*) FILTER (WHERE (reporting_patient_states.months_since_registration = (0)::double precision)) AS monthly_hypertension_and_diabetes_registrations + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.hypertension = 'yes'::text) AND (reporting_patient_states.diabetes = 'yes'::text)) + GROUP BY reporting_patient_states.registration_facility_region_id, reporting_patient_states.month_date + ), assigned_patients AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'dead'::text)) AS dead, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state <> 'dead'::text)) AS cumulative_assigned_patients + FROM public.reporting_patient_states + WHERE (reporting_patient_states.hypertension = 'yes'::text) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), assigned_diabetes_patients AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS diabetes_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS diabetes_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'dead'::text)) AS diabetes_dead, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state <> 'dead'::text)) AS cumulative_assigned_diabetic_patients + FROM public.reporting_patient_states + WHERE (reporting_patient_states.diabetes = 'yes'::text) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), adjusted_outcomes AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'controlled'::text))) AS controlled_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'uncontrolled'::text))) AS uncontrolled_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS missed_visit_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'visited_no_bp'::text))) AS visited_no_bp_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS missed_visit_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'visited_no_bp'::text))) AS visited_no_bp_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS patients_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS patients_lost_to_follow_up + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.hypertension = 'yes'::text) AND (reporting_patient_states.months_since_registration >= (3)::double precision)) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), adjusted_diabetes_outcomes AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'random'::text))) AS random_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'post_prandial'::text))) AS post_prandial_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'fasting'::text))) AS fasting_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'hba1c'::text))) AS hba1c_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text))) AS bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'random'::text))) AS random_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'post_prandial'::text))) AS post_prandial_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'fasting'::text))) AS fasting_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'hba1c'::text))) AS hba1c_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text))) AS bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'random'::text))) AS random_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'post_prandial'::text))) AS post_prandial_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'fasting'::text))) AS fasting_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'hba1c'::text))) AS hba1c_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text))) AS bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS bs_missed_visit_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'visited_no_bs'::text))) AS visited_no_bs_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS bs_missed_visit_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS diabetes_patients_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS diabetes_patients_lost_to_follow_up + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.diabetes = 'yes'::text) AND (reporting_patient_states.months_since_registration >= (3)::double precision)) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), monthly_cohort_outcomes AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'controlled'::text)) AS controlled, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'uncontrolled'::text)) AS uncontrolled, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'missed_visit'::text)) AS missed_visit, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'visited_no_bp'::text)) AS visited_no_bp, + count(DISTINCT reporting_patient_states.patient_id) AS patients + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.hypertension = 'yes'::text) AND (reporting_patient_states.months_since_registration = (2)::double precision)) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), monthly_overdue_calls AS ( + SELECT reporting_overdue_calls.appointment_facility_region_id AS region_id, + reporting_overdue_calls.month_date, + count(DISTINCT reporting_overdue_calls.appointment_id) AS call_results + FROM public.reporting_overdue_calls + GROUP BY reporting_overdue_calls.appointment_facility_region_id, reporting_overdue_calls.month_date + ), monthly_follow_ups AS ( + SELECT reporting_patient_follow_ups.facility_id, + reporting_patient_follow_ups.month_date, + count(DISTINCT reporting_patient_follow_ups.patient_id) AS follow_ups + FROM public.reporting_patient_follow_ups + WHERE (reporting_patient_follow_ups.hypertension = 'yes'::text) + GROUP BY reporting_patient_follow_ups.facility_id, reporting_patient_follow_ups.month_date + ), monthly_diabetes_follow_ups AS ( + SELECT reporting_patient_follow_ups.facility_id, + reporting_patient_follow_ups.month_date, + count(DISTINCT reporting_patient_follow_ups.patient_id) AS follow_ups + FROM public.reporting_patient_follow_ups + WHERE (reporting_patient_follow_ups.diabetes = 'yes'::text) + GROUP BY reporting_patient_follow_ups.facility_id, reporting_patient_follow_ups.month_date + ), monthly_hypertension_overdue_patients AS ( + SELECT reporting_overdue_patients.assigned_facility_region_id, + reporting_overdue_patients.month_date, + count(*) FILTER (WHERE (reporting_overdue_patients.is_overdue = 'yes'::text)) AS overdue_patients, + count(*) FILTER (WHERE ((reporting_overdue_patients.is_overdue = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text))) AS contactable_overdue_patients, + count(*) FILTER (WHERE (reporting_overdue_patients.has_called = 'yes'::text)) AS patients_called, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS patients_called_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS patients_called_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS patients_called_with_result_removed_from_list, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text))) AS contactable_patients_called, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS contactable_patients_called_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS contactable_patients_called_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS contactable_patients_called_with_result_removed_from_list, + count(*) FILTER (WHERE (reporting_overdue_patients.has_visited_following_call = 'yes'::text)) AS patients_returned_after_call, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS patients_returned_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS patients_returned_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS patients_returned_with_result_removed_from_list, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text))) AS contactable_patients_returned_after_call, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS contactable_patients_returned_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS contactable_patients_returned_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS contactable_patients_returned_with_result_removed_from_list + FROM public.reporting_overdue_patients + WHERE ((reporting_overdue_patients.hypertension = 'yes'::text) AND (reporting_overdue_patients.under_care = 'yes'::text)) + GROUP BY reporting_overdue_patients.assigned_facility_region_id, reporting_overdue_patients.month_date + ) + SELECT cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string, + rf.facility_id, + rf.facility_name, + rf.facility_type, + rf.facility_size, + rf.facility_region_id, + rf.facility_region_name, + rf.facility_region_slug, + rf.block_region_id, + rf.block_name, + rf.block_slug, + rf.district_id, + rf.district_region_id, + rf.district_name, + rf.district_slug, + rf.state_region_id, + rf.state_name, + rf.state_slug, + rf.organization_id, + rf.organization_region_id, + rf.organization_name, + rf.organization_slug, + registered_patients.cumulative_registrations, + registered_patients.monthly_registrations, + registered_diabetes_patients.cumulative_diabetes_registrations, + registered_diabetes_patients.monthly_diabetes_registrations, + registered_hypertension_and_diabetes_patients.cumulative_hypertension_and_diabetes_registrations, + registered_hypertension_and_diabetes_patients.monthly_hypertension_and_diabetes_registrations, + assigned_patients.under_care, + assigned_patients.lost_to_follow_up, + assigned_patients.dead, + assigned_patients.cumulative_assigned_patients, + assigned_diabetes_patients.diabetes_under_care, + assigned_diabetes_patients.diabetes_lost_to_follow_up, + assigned_diabetes_patients.diabetes_dead, + assigned_diabetes_patients.cumulative_assigned_diabetic_patients, + adjusted_outcomes.controlled_under_care AS adjusted_controlled_under_care, + adjusted_outcomes.uncontrolled_under_care AS adjusted_uncontrolled_under_care, + adjusted_outcomes.missed_visit_under_care AS adjusted_missed_visit_under_care, + adjusted_outcomes.visited_no_bp_under_care AS adjusted_visited_no_bp_under_care, + adjusted_outcomes.missed_visit_lost_to_follow_up AS adjusted_missed_visit_lost_to_follow_up, + adjusted_outcomes.visited_no_bp_lost_to_follow_up AS adjusted_visited_no_bp_lost_to_follow_up, + adjusted_outcomes.patients_under_care AS adjusted_patients_under_care, + adjusted_outcomes.patients_lost_to_follow_up AS adjusted_patients_lost_to_follow_up, + adjusted_diabetes_outcomes.random_bs_below_200_under_care AS adjusted_random_bs_below_200_under_care, + adjusted_diabetes_outcomes.fasting_bs_below_200_under_care AS adjusted_fasting_bs_below_200_under_care, + adjusted_diabetes_outcomes.post_prandial_bs_below_200_under_care AS adjusted_post_prandial_bs_below_200_under_care, + adjusted_diabetes_outcomes.hba1c_bs_below_200_under_care AS adjusted_hba1c_bs_below_200_under_care, + adjusted_diabetes_outcomes.bs_below_200_under_care AS adjusted_bs_below_200_under_care, + adjusted_diabetes_outcomes.random_bs_200_to_300_under_care AS adjusted_random_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.fasting_bs_200_to_300_under_care AS adjusted_fasting_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.post_prandial_bs_200_to_300_under_care AS adjusted_post_prandial_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.hba1c_bs_200_to_300_under_care AS adjusted_hba1c_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.bs_200_to_300_under_care AS adjusted_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.random_bs_over_300_under_care AS adjusted_random_bs_over_300_under_care, + adjusted_diabetes_outcomes.fasting_bs_over_300_under_care AS adjusted_fasting_bs_over_300_under_care, + adjusted_diabetes_outcomes.post_prandial_bs_over_300_under_care AS adjusted_post_prandial_bs_over_300_under_care, + adjusted_diabetes_outcomes.hba1c_bs_over_300_under_care AS adjusted_hba1c_bs_over_300_under_care, + adjusted_diabetes_outcomes.bs_over_300_under_care AS adjusted_bs_over_300_under_care, + adjusted_diabetes_outcomes.bs_missed_visit_under_care AS adjusted_bs_missed_visit_under_care, + adjusted_diabetes_outcomes.visited_no_bs_under_care AS adjusted_visited_no_bs_under_care, + adjusted_diabetes_outcomes.bs_missed_visit_lost_to_follow_up AS adjusted_bs_missed_visit_lost_to_follow_up, + adjusted_diabetes_outcomes.diabetes_patients_under_care AS adjusted_diabetes_patients_under_care, + adjusted_diabetes_outcomes.diabetes_patients_lost_to_follow_up AS adjusted_diabetes_patients_lost_to_follow_up, + monthly_cohort_outcomes.controlled AS monthly_cohort_controlled, + monthly_cohort_outcomes.uncontrolled AS monthly_cohort_uncontrolled, + monthly_cohort_outcomes.missed_visit AS monthly_cohort_missed_visit, + monthly_cohort_outcomes.visited_no_bp AS monthly_cohort_visited_no_bp, + monthly_cohort_outcomes.patients AS monthly_cohort_patients, + monthly_overdue_calls.call_results AS monthly_overdue_calls, + monthly_follow_ups.follow_ups AS monthly_follow_ups, + monthly_diabetes_follow_ups.follow_ups AS monthly_diabetes_follow_ups, + reporting_facility_appointment_scheduled_days.htn_total_appts_scheduled AS total_appts_scheduled, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_0_to_14_days AS appts_scheduled_0_to_14_days, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_15_to_31_days AS appts_scheduled_15_to_31_days, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_32_to_62_days AS appts_scheduled_32_to_62_days, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_more_than_62_days AS appts_scheduled_more_than_62_days, + reporting_facility_appointment_scheduled_days.diabetes_total_appts_scheduled, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_0_to_14_days, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_15_to_31_days, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_32_to_62_days, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_more_than_62_days, + monthly_hypertension_overdue_patients.overdue_patients, + monthly_hypertension_overdue_patients.contactable_overdue_patients, + monthly_hypertension_overdue_patients.patients_called, + monthly_hypertension_overdue_patients.contactable_patients_called, + monthly_hypertension_overdue_patients.patients_called_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.patients_called_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.patients_called_with_result_removed_from_list, + monthly_hypertension_overdue_patients.contactable_patients_called_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.contactable_patients_called_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.contactable_patients_called_with_result_removed_from_list, + monthly_hypertension_overdue_patients.patients_returned_after_call, + monthly_hypertension_overdue_patients.contactable_patients_returned_after_call, + monthly_hypertension_overdue_patients.patients_returned_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.patients_returned_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.patients_returned_with_result_removed_from_list, + monthly_hypertension_overdue_patients.contactable_patients_returned_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.contactable_patients_returned_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.contactable_patients_returned_with_result_removed_from_list + FROM ((((((((((((((public.reporting_facilities rf + JOIN public.reporting_months cal ON (true)) + LEFT JOIN registered_patients ON (((registered_patients.month_date = cal.month_date) AND (registered_patients.region_id = rf.facility_region_id)))) + LEFT JOIN registered_diabetes_patients ON (((registered_diabetes_patients.month_date = cal.month_date) AND (registered_diabetes_patients.region_id = rf.facility_region_id)))) + LEFT JOIN registered_hypertension_and_diabetes_patients ON (((registered_hypertension_and_diabetes_patients.month_date = cal.month_date) AND (registered_hypertension_and_diabetes_patients.region_id = rf.facility_region_id)))) + LEFT JOIN assigned_patients ON (((assigned_patients.month_date = cal.month_date) AND (assigned_patients.region_id = rf.facility_region_id)))) + LEFT JOIN assigned_diabetes_patients ON (((assigned_diabetes_patients.month_date = cal.month_date) AND (assigned_diabetes_patients.region_id = rf.facility_region_id)))) + LEFT JOIN adjusted_outcomes ON (((adjusted_outcomes.month_date = cal.month_date) AND (adjusted_outcomes.region_id = rf.facility_region_id)))) + LEFT JOIN adjusted_diabetes_outcomes ON (((adjusted_diabetes_outcomes.month_date = cal.month_date) AND (adjusted_diabetes_outcomes.region_id = rf.facility_region_id)))) + LEFT JOIN monthly_cohort_outcomes ON (((monthly_cohort_outcomes.month_date = cal.month_date) AND (monthly_cohort_outcomes.region_id = rf.facility_region_id)))) + LEFT JOIN monthly_overdue_calls ON (((monthly_overdue_calls.month_date = cal.month_date) AND (monthly_overdue_calls.region_id = rf.facility_region_id)))) + LEFT JOIN monthly_follow_ups ON (((monthly_follow_ups.month_date = cal.month_date) AND (monthly_follow_ups.facility_id = rf.facility_id)))) + LEFT JOIN monthly_diabetes_follow_ups ON (((monthly_diabetes_follow_ups.month_date = cal.month_date) AND (monthly_diabetes_follow_ups.facility_id = rf.facility_id)))) + LEFT JOIN public.reporting_facility_appointment_scheduled_days ON (((reporting_facility_appointment_scheduled_days.month_date = cal.month_date) AND (reporting_facility_appointment_scheduled_days.facility_id = rf.facility_id)))) + LEFT JOIN monthly_hypertension_overdue_patients ON (((monthly_hypertension_overdue_patients.month_date = cal.month_date) AND (monthly_hypertension_overdue_patients.assigned_facility_region_id = rf.facility_region_id)))) + WITH NO DATA; + SQL + + add_index "reporting_facility_states", ["block_region_id", "month_date"], name: "index_fs_block_month_date" + add_index "reporting_facility_states", ["district_region_id", "month_date"], name: "index_fs_district_month_date" + add_index "reporting_facility_states", ["month_date", "facility_region_id"], name: "index_fs_month_date_region_id", unique: true + add_index "reporting_facility_states", ["organization_region_id", "month_date"], name: "index_fs_organization_month_date" + add_index "reporting_facility_states", ["state_region_id", "month_date"], name: "index_fs_state_month_date" + end + + def down + drop_view :reporting_facility_states, materialized: true + drop_view :reporting_facility_appointment_scheduled_days, materialized: true + + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_facility_appointment_scheduled_days AS + WITH latest_medical_histories AS ( + SELECT DISTINCT ON (patient_id) mh.* + FROM medical_histories mh + WHERE mh.deleted_at IS NULL + ORDER BY patient_id, mh.device_created_at DESC + ), + latest_appointments_per_patient_per_month AS ( + SELECT DISTINCT ON (patient_id, month_date) a.*, + lmh.hypertension, + lmh.diabetes, + to_char(a.device_created_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')), 'YYYY-MM-01')::date month_date + FROM appointments a + INNER JOIN patients p ON p.id = a.patient_id + INNER JOIN latest_medical_histories lmh ON lmh.patient_id = a.patient_id + WHERE a.scheduled_date >= date_trunc('day', a.device_created_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE'))) + AND a.device_created_at >= date_trunc('month', (now() AT TIME ZONE 'UTC') - INTERVAL '6 months') + AND date_trunc('month', a.device_created_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE'))) + > date_trunc('month', p.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE'))) + AND p.deleted_at IS NULL and a.deleted_at IS NULL + AND (lmh.hypertension = 'yes' OR lmh.diabetes = 'yes') + ORDER BY a.patient_id, month_date, a.device_created_at desc + ), + scheduled_days_distribution AS ( + SELECT month_date, + width_bucket( + extract('days' FROM (scheduled_date - date_trunc('day', device_created_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))))::integer, + array[0, 15, 32, 63] + ) bucket, + COUNT(*) number_of_appointments, + hypertension, + diabetes, + creation_facility_id facility_id + FROM latest_appointments_per_patient_per_month + GROUP BY bucket, creation_facility_id, month_date, hypertension, diabetes + ) + SELECT facility_id, + month_date, + (SUM(number_of_appointments) FILTER (WHERE bucket = 1 AND hypertension = 'yes'))::integer AS htn_appts_scheduled_0_to_14_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 2 AND hypertension = 'yes'))::integer AS htn_appts_scheduled_15_to_31_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 3 AND hypertension = 'yes'))::integer AS htn_appts_scheduled_32_to_62_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 4 AND hypertension = 'yes'))::integer AS htn_appts_scheduled_more_than_62_days, + (SUM(number_of_appointments) FILTER (WHERE hypertension = 'yes'))::integer htn_total_appts_scheduled, + + (SUM(number_of_appointments) FILTER (WHERE bucket = 1 AND diabetes = 'yes'))::integer AS diabetes_appts_scheduled_0_to_14_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 2 AND diabetes = 'yes'))::integer AS diabetes_appts_scheduled_15_to_31_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 3 AND diabetes = 'yes'))::integer AS diabetes_appts_scheduled_32_to_62_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 4 AND diabetes = 'yes'))::integer AS diabetes_appts_scheduled_more_than_62_days, + (SUM(number_of_appointments) FILTER (WHERE diabetes = 'yes'))::integer diabetes_total_appts_scheduled + FROM scheduled_days_distribution + GROUP BY facility_id, month_date + WITH NO DATA; + + SQL + + add_index :reporting_facility_appointment_scheduled_days, [:month_date, :facility_id], name: "index_reporting_facility_appointment_scheduled_days", unique: true + + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_facility_states AS + WITH registered_patients AS ( + SELECT reporting_patient_states.registration_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(*) AS cumulative_registrations, + count(*) FILTER (WHERE (reporting_patient_states.months_since_registration = (0)::double precision)) AS monthly_registrations + FROM public.reporting_patient_states + WHERE (reporting_patient_states.hypertension = 'yes'::text) + GROUP BY reporting_patient_states.registration_facility_region_id, reporting_patient_states.month_date + ), registered_diabetes_patients AS ( + SELECT reporting_patient_states.registration_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(*) AS cumulative_diabetes_registrations, + count(*) FILTER (WHERE (reporting_patient_states.months_since_registration = (0)::double precision)) AS monthly_diabetes_registrations + FROM public.reporting_patient_states + WHERE (reporting_patient_states.diabetes = 'yes'::text) + GROUP BY reporting_patient_states.registration_facility_region_id, reporting_patient_states.month_date + ), registered_hypertension_and_diabetes_patients AS ( + SELECT reporting_patient_states.registration_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(*) AS cumulative_hypertension_and_diabetes_registrations, + count(*) FILTER (WHERE (reporting_patient_states.months_since_registration = (0)::double precision)) AS monthly_hypertension_and_diabetes_registrations + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.hypertension = 'yes'::text) AND (reporting_patient_states.diabetes = 'yes'::text)) + GROUP BY reporting_patient_states.registration_facility_region_id, reporting_patient_states.month_date + ), assigned_patients AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'dead'::text)) AS dead, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state <> 'dead'::text)) AS cumulative_assigned_patients + FROM public.reporting_patient_states + WHERE (reporting_patient_states.hypertension = 'yes'::text) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), assigned_diabetes_patients AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS diabetes_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS diabetes_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'dead'::text)) AS diabetes_dead, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state <> 'dead'::text)) AS cumulative_assigned_diabetic_patients + FROM public.reporting_patient_states + WHERE (reporting_patient_states.diabetes = 'yes'::text) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), adjusted_outcomes AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'controlled'::text))) AS controlled_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'uncontrolled'::text))) AS uncontrolled_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS missed_visit_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'visited_no_bp'::text))) AS visited_no_bp_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS missed_visit_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text) AND (reporting_patient_states.htn_treatment_outcome_in_last_3_months = 'visited_no_bp'::text))) AS visited_no_bp_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS patients_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS patients_lost_to_follow_up + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.hypertension = 'yes'::text) AND (reporting_patient_states.months_since_registration >= (3)::double precision)) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), adjusted_diabetes_outcomes AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'random'::text))) AS random_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'post_prandial'::text))) AS post_prandial_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'fasting'::text))) AS fasting_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'hba1c'::text))) AS hba1c_bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_below_200'::text))) AS bs_below_200_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'random'::text))) AS random_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'post_prandial'::text))) AS post_prandial_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'fasting'::text))) AS fasting_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'hba1c'::text))) AS hba1c_bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_200_to_300'::text))) AS bs_200_to_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'random'::text))) AS random_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'post_prandial'::text))) AS post_prandial_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'fasting'::text))) AS fasting_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text) AND ((reporting_patient_states.blood_sugar_type)::text = 'hba1c'::text))) AS hba1c_bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'bs_over_300'::text))) AS bs_over_300_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS bs_missed_visit_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'under_care'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'visited_no_bs'::text))) AS visited_no_bs_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE ((reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text) AND (reporting_patient_states.diabetes_treatment_outcome_in_last_3_months = 'missed_visit'::text))) AS bs_missed_visit_lost_to_follow_up, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'under_care'::text)) AS diabetes_patients_under_care, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_care_state = 'lost_to_follow_up'::text)) AS diabetes_patients_lost_to_follow_up + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.diabetes = 'yes'::text) AND (reporting_patient_states.months_since_registration >= (3)::double precision)) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), monthly_cohort_outcomes AS ( + SELECT reporting_patient_states.assigned_facility_region_id AS region_id, + reporting_patient_states.month_date, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'controlled'::text)) AS controlled, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'uncontrolled'::text)) AS uncontrolled, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'missed_visit'::text)) AS missed_visit, + count(DISTINCT reporting_patient_states.patient_id) FILTER (WHERE (reporting_patient_states.htn_treatment_outcome_in_last_2_months = 'visited_no_bp'::text)) AS visited_no_bp, + count(DISTINCT reporting_patient_states.patient_id) AS patients + FROM public.reporting_patient_states + WHERE ((reporting_patient_states.hypertension = 'yes'::text) AND (reporting_patient_states.months_since_registration = (2)::double precision)) + GROUP BY reporting_patient_states.assigned_facility_region_id, reporting_patient_states.month_date + ), monthly_overdue_calls AS ( + SELECT reporting_overdue_calls.appointment_facility_region_id AS region_id, + reporting_overdue_calls.month_date, + count(DISTINCT reporting_overdue_calls.appointment_id) AS call_results + FROM public.reporting_overdue_calls + GROUP BY reporting_overdue_calls.appointment_facility_region_id, reporting_overdue_calls.month_date + ), monthly_follow_ups AS ( + SELECT reporting_patient_follow_ups.facility_id, + reporting_patient_follow_ups.month_date, + count(DISTINCT reporting_patient_follow_ups.patient_id) AS follow_ups + FROM public.reporting_patient_follow_ups + WHERE (reporting_patient_follow_ups.hypertension = 'yes'::text) + GROUP BY reporting_patient_follow_ups.facility_id, reporting_patient_follow_ups.month_date + ), monthly_diabetes_follow_ups AS ( + SELECT reporting_patient_follow_ups.facility_id, + reporting_patient_follow_ups.month_date, + count(DISTINCT reporting_patient_follow_ups.patient_id) AS follow_ups + FROM public.reporting_patient_follow_ups + WHERE (reporting_patient_follow_ups.diabetes = 'yes'::text) + GROUP BY reporting_patient_follow_ups.facility_id, reporting_patient_follow_ups.month_date + ), monthly_hypertension_overdue_patients AS ( + SELECT reporting_overdue_patients.assigned_facility_region_id, + reporting_overdue_patients.month_date, + count(*) FILTER (WHERE (reporting_overdue_patients.is_overdue = 'yes'::text)) AS overdue_patients, + count(*) FILTER (WHERE ((reporting_overdue_patients.is_overdue = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text))) AS contactable_overdue_patients, + count(*) FILTER (WHERE (reporting_overdue_patients.has_called = 'yes'::text)) AS patients_called, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS patients_called_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS patients_called_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS patients_called_with_result_removed_from_list, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text))) AS contactable_patients_called, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS contactable_patients_called_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS contactable_patients_called_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_called = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS contactable_patients_called_with_result_removed_from_list, + count(*) FILTER (WHERE (reporting_overdue_patients.has_visited_following_call = 'yes'::text)) AS patients_returned_after_call, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS patients_returned_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS patients_returned_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS patients_returned_with_result_removed_from_list, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text))) AS contactable_patients_returned_after_call, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'agreed_to_visit'::text))) AS contactable_patients_returned_with_result_agreed_to_visit, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'remind_to_call_later'::text))) AS contactable_patients_returned_with_result_remind_to_call_later, + count(*) FILTER (WHERE ((reporting_overdue_patients.has_visited_following_call = 'yes'::text) AND (reporting_overdue_patients.removed_from_overdue_list = 'no'::text) AND (reporting_overdue_patients.has_phone = 'yes'::text) AND ((reporting_overdue_patients.next_call_result_type)::text = 'removed_from_overdue_list'::text))) AS contactable_patients_returned_with_result_removed_from_list + FROM public.reporting_overdue_patients + WHERE ((reporting_overdue_patients.hypertension = 'yes'::text) AND (reporting_overdue_patients.under_care = 'yes'::text)) + GROUP BY reporting_overdue_patients.assigned_facility_region_id, reporting_overdue_patients.month_date + ) + SELECT cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string, + rf.facility_id, + rf.facility_name, + rf.facility_type, + rf.facility_size, + rf.facility_region_id, + rf.facility_region_name, + rf.facility_region_slug, + rf.block_region_id, + rf.block_name, + rf.block_slug, + rf.district_id, + rf.district_region_id, + rf.district_name, + rf.district_slug, + rf.state_region_id, + rf.state_name, + rf.state_slug, + rf.organization_id, + rf.organization_region_id, + rf.organization_name, + rf.organization_slug, + registered_patients.cumulative_registrations, + registered_patients.monthly_registrations, + registered_diabetes_patients.cumulative_diabetes_registrations, + registered_diabetes_patients.monthly_diabetes_registrations, + registered_hypertension_and_diabetes_patients.cumulative_hypertension_and_diabetes_registrations, + registered_hypertension_and_diabetes_patients.monthly_hypertension_and_diabetes_registrations, + assigned_patients.under_care, + assigned_patients.lost_to_follow_up, + assigned_patients.dead, + assigned_patients.cumulative_assigned_patients, + assigned_diabetes_patients.diabetes_under_care, + assigned_diabetes_patients.diabetes_lost_to_follow_up, + assigned_diabetes_patients.diabetes_dead, + assigned_diabetes_patients.cumulative_assigned_diabetic_patients, + adjusted_outcomes.controlled_under_care AS adjusted_controlled_under_care, + adjusted_outcomes.uncontrolled_under_care AS adjusted_uncontrolled_under_care, + adjusted_outcomes.missed_visit_under_care AS adjusted_missed_visit_under_care, + adjusted_outcomes.visited_no_bp_under_care AS adjusted_visited_no_bp_under_care, + adjusted_outcomes.missed_visit_lost_to_follow_up AS adjusted_missed_visit_lost_to_follow_up, + adjusted_outcomes.visited_no_bp_lost_to_follow_up AS adjusted_visited_no_bp_lost_to_follow_up, + adjusted_outcomes.patients_under_care AS adjusted_patients_under_care, + adjusted_outcomes.patients_lost_to_follow_up AS adjusted_patients_lost_to_follow_up, + adjusted_diabetes_outcomes.random_bs_below_200_under_care AS adjusted_random_bs_below_200_under_care, + adjusted_diabetes_outcomes.fasting_bs_below_200_under_care AS adjusted_fasting_bs_below_200_under_care, + adjusted_diabetes_outcomes.post_prandial_bs_below_200_under_care AS adjusted_post_prandial_bs_below_200_under_care, + adjusted_diabetes_outcomes.hba1c_bs_below_200_under_care AS adjusted_hba1c_bs_below_200_under_care, + adjusted_diabetes_outcomes.bs_below_200_under_care AS adjusted_bs_below_200_under_care, + adjusted_diabetes_outcomes.random_bs_200_to_300_under_care AS adjusted_random_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.fasting_bs_200_to_300_under_care AS adjusted_fasting_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.post_prandial_bs_200_to_300_under_care AS adjusted_post_prandial_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.hba1c_bs_200_to_300_under_care AS adjusted_hba1c_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.bs_200_to_300_under_care AS adjusted_bs_200_to_300_under_care, + adjusted_diabetes_outcomes.random_bs_over_300_under_care AS adjusted_random_bs_over_300_under_care, + adjusted_diabetes_outcomes.fasting_bs_over_300_under_care AS adjusted_fasting_bs_over_300_under_care, + adjusted_diabetes_outcomes.post_prandial_bs_over_300_under_care AS adjusted_post_prandial_bs_over_300_under_care, + adjusted_diabetes_outcomes.hba1c_bs_over_300_under_care AS adjusted_hba1c_bs_over_300_under_care, + adjusted_diabetes_outcomes.bs_over_300_under_care AS adjusted_bs_over_300_under_care, + adjusted_diabetes_outcomes.bs_missed_visit_under_care AS adjusted_bs_missed_visit_under_care, + adjusted_diabetes_outcomes.visited_no_bs_under_care AS adjusted_visited_no_bs_under_care, + adjusted_diabetes_outcomes.bs_missed_visit_lost_to_follow_up AS adjusted_bs_missed_visit_lost_to_follow_up, + adjusted_diabetes_outcomes.diabetes_patients_under_care AS adjusted_diabetes_patients_under_care, + adjusted_diabetes_outcomes.diabetes_patients_lost_to_follow_up AS adjusted_diabetes_patients_lost_to_follow_up, + monthly_cohort_outcomes.controlled AS monthly_cohort_controlled, + monthly_cohort_outcomes.uncontrolled AS monthly_cohort_uncontrolled, + monthly_cohort_outcomes.missed_visit AS monthly_cohort_missed_visit, + monthly_cohort_outcomes.visited_no_bp AS monthly_cohort_visited_no_bp, + monthly_cohort_outcomes.patients AS monthly_cohort_patients, + monthly_overdue_calls.call_results AS monthly_overdue_calls, + monthly_follow_ups.follow_ups AS monthly_follow_ups, + monthly_diabetes_follow_ups.follow_ups AS monthly_diabetes_follow_ups, + reporting_facility_appointment_scheduled_days.htn_total_appts_scheduled AS total_appts_scheduled, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_0_to_14_days AS appts_scheduled_0_to_14_days, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_15_to_31_days AS appts_scheduled_15_to_31_days, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_32_to_62_days AS appts_scheduled_32_to_62_days, + reporting_facility_appointment_scheduled_days.htn_appts_scheduled_more_than_62_days AS appts_scheduled_more_than_62_days, + reporting_facility_appointment_scheduled_days.diabetes_total_appts_scheduled, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_0_to_14_days, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_15_to_31_days, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_32_to_62_days, + reporting_facility_appointment_scheduled_days.diabetes_appts_scheduled_more_than_62_days, + monthly_hypertension_overdue_patients.overdue_patients, + monthly_hypertension_overdue_patients.contactable_overdue_patients, + monthly_hypertension_overdue_patients.patients_called, + monthly_hypertension_overdue_patients.contactable_patients_called, + monthly_hypertension_overdue_patients.patients_called_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.patients_called_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.patients_called_with_result_removed_from_list, + monthly_hypertension_overdue_patients.contactable_patients_called_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.contactable_patients_called_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.contactable_patients_called_with_result_removed_from_list, + monthly_hypertension_overdue_patients.patients_returned_after_call, + monthly_hypertension_overdue_patients.contactable_patients_returned_after_call, + monthly_hypertension_overdue_patients.patients_returned_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.patients_returned_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.patients_returned_with_result_removed_from_list, + monthly_hypertension_overdue_patients.contactable_patients_returned_with_result_agreed_to_visit, + monthly_hypertension_overdue_patients.contactable_patients_returned_with_result_remind_to_call_later, + monthly_hypertension_overdue_patients.contactable_patients_returned_with_result_removed_from_list + FROM ((((((((((((((public.reporting_facilities rf + JOIN public.reporting_months cal ON (true)) + LEFT JOIN registered_patients ON (((registered_patients.month_date = cal.month_date) AND (registered_patients.region_id = rf.facility_region_id)))) + LEFT JOIN registered_diabetes_patients ON (((registered_diabetes_patients.month_date = cal.month_date) AND (registered_diabetes_patients.region_id = rf.facility_region_id)))) + LEFT JOIN registered_hypertension_and_diabetes_patients ON (((registered_hypertension_and_diabetes_patients.month_date = cal.month_date) AND (registered_hypertension_and_diabetes_patients.region_id = rf.facility_region_id)))) + LEFT JOIN assigned_patients ON (((assigned_patients.month_date = cal.month_date) AND (assigned_patients.region_id = rf.facility_region_id)))) + LEFT JOIN assigned_diabetes_patients ON (((assigned_diabetes_patients.month_date = cal.month_date) AND (assigned_diabetes_patients.region_id = rf.facility_region_id)))) + LEFT JOIN adjusted_outcomes ON (((adjusted_outcomes.month_date = cal.month_date) AND (adjusted_outcomes.region_id = rf.facility_region_id)))) + LEFT JOIN adjusted_diabetes_outcomes ON (((adjusted_diabetes_outcomes.month_date = cal.month_date) AND (adjusted_diabetes_outcomes.region_id = rf.facility_region_id)))) + LEFT JOIN monthly_cohort_outcomes ON (((monthly_cohort_outcomes.month_date = cal.month_date) AND (monthly_cohort_outcomes.region_id = rf.facility_region_id)))) + LEFT JOIN monthly_overdue_calls ON (((monthly_overdue_calls.month_date = cal.month_date) AND (monthly_overdue_calls.region_id = rf.facility_region_id)))) + LEFT JOIN monthly_follow_ups ON (((monthly_follow_ups.month_date = cal.month_date) AND (monthly_follow_ups.facility_id = rf.facility_id)))) + LEFT JOIN monthly_diabetes_follow_ups ON (((monthly_diabetes_follow_ups.month_date = cal.month_date) AND (monthly_diabetes_follow_ups.facility_id = rf.facility_id)))) + LEFT JOIN public.reporting_facility_appointment_scheduled_days ON (((reporting_facility_appointment_scheduled_days.month_date = cal.month_date) AND (reporting_facility_appointment_scheduled_days.facility_id = rf.facility_id)))) + LEFT JOIN monthly_hypertension_overdue_patients ON (((monthly_hypertension_overdue_patients.month_date = cal.month_date) AND (monthly_hypertension_overdue_patients.assigned_facility_region_id = rf.facility_region_id)))) + WITH NO DATA; + SQL + + add_index "reporting_facility_states", ["block_region_id", "month_date"], name: "index_fs_block_month_date" + add_index "reporting_facility_states", ["district_region_id", "month_date"], name: "index_fs_district_month_date" + add_index "reporting_facility_states", ["month_date", "facility_region_id"], name: "index_fs_month_date_region_id", unique: true + add_index "reporting_facility_states", ["organization_region_id", "month_date"], name: "index_fs_organization_month_date" + add_index "reporting_facility_states", ["state_region_id", "month_date"], name: "index_fs_state_month_date" + end +end diff --git a/db/migrate/20251210061204_update_reporting_facility_daily_follow_ups_and_registrations_to_version3.rb b/db/migrate/20251210061204_update_reporting_facility_daily_follow_ups_and_registrations_to_version3.rb new file mode 100644 index 0000000000..4af67e8a85 --- /dev/null +++ b/db/migrate/20251210061204_update_reporting_facility_daily_follow_ups_and_registrations_to_version3.rb @@ -0,0 +1,433 @@ +class UpdateReportingFacilityDailyFollowUpsAndRegistrationsToVersion3 < ActiveRecord::Migration[6.1] + def up + drop_view :reporting_facility_daily_follow_ups_and_registrations, materialized: true + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_facility_daily_follow_ups_and_registrations AS + WITH follow_up_blood_pressures AS ( + SELECT DISTINCT ON (((EXTRACT(doy FROM ((bp.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), bp.facility_id, p.id) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + bp.id AS visit_id, + 'BloodPressure'::text AS visit_type, + bp.facility_id, + bp.user_id, + bp.recorded_at AS visited_at, + (EXTRACT(doy FROM ((bp.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year + FROM (public.patients p + JOIN public.blood_pressures bp ON (((p.id = bp.patient_id) AND (date_trunc('day'::text, ((bp.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.diagnosed_confirmed_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) + WHERE ((p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) AND (bp.recorded_at > (CURRENT_TIMESTAMP - '30 days'::interval))) + ), follow_up_blood_sugars AS ( + SELECT DISTINCT ON (((EXTRACT(doy FROM ((bs.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), bs.facility_id, p.id) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + bs.id AS visit_id, + 'BloodSugar'::text AS visit_type, + bs.facility_id, + bs.user_id, + bs.recorded_at AS visited_at, + (EXTRACT(doy FROM ((bs.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year + FROM (public.patients p + JOIN public.blood_sugars bs ON (((p.id = bs.patient_id) AND (date_trunc('day'::text, ((bs.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.diagnosed_confirmed_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) + WHERE ((p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) AND (bs.recorded_at > (CURRENT_TIMESTAMP - '30 days'::interval))) + ), follow_up_prescription_drugs AS ( + SELECT DISTINCT ON (((EXTRACT(doy FROM ((pd.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), pd.facility_id, p.id) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + pd.id AS visit_id, + 'PrescriptionDrug'::text AS visit_type, + pd.facility_id, + pd.user_id, + pd.device_created_at AS visited_at, + (EXTRACT(doy FROM ((pd.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year + FROM (public.patients p + JOIN public.prescription_drugs pd ON (((p.id = pd.patient_id) AND (date_trunc('day'::text, ((pd.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.diagnosed_confirmed_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) + WHERE ((p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) AND (pd.device_created_at > (CURRENT_TIMESTAMP - '30 days'::interval))) + ), follow_up_appointments AS ( + SELECT DISTINCT ON (((EXTRACT(doy FROM ((app.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), app.creation_facility_id, p.id) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + app.id AS visit_id, + 'Appointment'::text AS visit_type, + app.creation_facility_id AS facility_id, + app.user_id, + app.device_created_at AS visited_at, + (EXTRACT(doy FROM ((app.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year + FROM (public.patients p + JOIN public.appointments app ON (((p.id = app.patient_id) AND (date_trunc('day'::text, ((app.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.diagnosed_confirmed_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) + WHERE ((p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) AND (app.device_created_at > (CURRENT_TIMESTAMP - '30 days'::interval))) + ), registered_patients AS ( + SELECT DISTINCT ON (((EXTRACT(doy FROM ((p.diagnosed_confirmed_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), p.registration_facility_id, p.id) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + p.id AS visit_id, + 'Registration'::text AS visit_type, + p.registration_facility_id AS facility_id, + p.registration_user_id AS user_id, + p.diagnosed_confirmed_at AS visited_at, + (EXTRACT(doy FROM ((p.diagnosed_confirmed_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year + FROM public.patients p + WHERE ((p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) AND (p.diagnosed_confirmed_at > (CURRENT_TIMESTAMP - '30 days'::interval))) + ), all_follow_ups AS ( + SELECT follow_up_blood_pressures.patient_id, + follow_up_blood_pressures.patient_gender, + follow_up_blood_pressures.visit_id, + follow_up_blood_pressures.visit_type, + follow_up_blood_pressures.facility_id, + follow_up_blood_pressures.user_id, + follow_up_blood_pressures.visited_at, + follow_up_blood_pressures.day_of_year + FROM follow_up_blood_pressures + UNION + SELECT follow_up_blood_sugars.patient_id, + follow_up_blood_sugars.patient_gender, + follow_up_blood_sugars.visit_id, + follow_up_blood_sugars.visit_type, + follow_up_blood_sugars.facility_id, + follow_up_blood_sugars.user_id, + follow_up_blood_sugars.visited_at, + follow_up_blood_sugars.day_of_year + FROM follow_up_blood_sugars + UNION + SELECT follow_up_prescription_drugs.patient_id, + follow_up_prescription_drugs.patient_gender, + follow_up_prescription_drugs.visit_id, + follow_up_prescription_drugs.visit_type, + follow_up_prescription_drugs.facility_id, + follow_up_prescription_drugs.user_id, + follow_up_prescription_drugs.visited_at, + follow_up_prescription_drugs.day_of_year + FROM follow_up_prescription_drugs + UNION + SELECT follow_up_appointments.patient_id, + follow_up_appointments.patient_gender, + follow_up_appointments.visit_id, + follow_up_appointments.visit_type, + follow_up_appointments.facility_id, + follow_up_appointments.user_id, + follow_up_appointments.visited_at, + follow_up_appointments.day_of_year + FROM follow_up_appointments + ), all_follow_ups_with_medical_histories AS ( + SELECT DISTINCT ON (all_follow_ups.day_of_year, all_follow_ups.facility_id, all_follow_ups.patient_id) all_follow_ups.patient_id, + all_follow_ups.patient_gender, + all_follow_ups.facility_id, + mh.diabetes, + mh.hypertension, + all_follow_ups.user_id, + all_follow_ups.visit_id, + all_follow_ups.visit_type, + all_follow_ups.visited_at, + all_follow_ups.day_of_year + FROM (all_follow_ups + JOIN public.medical_histories mh ON ((all_follow_ups.patient_id = mh.patient_id))) + ), registered_patients_with_medical_histories AS ( + SELECT DISTINCT ON (registered_patients.day_of_year, registered_patients.facility_id, registered_patients.patient_id) registered_patients.patient_id, + registered_patients.patient_gender, + registered_patients.facility_id, + mh.diabetes, + mh.hypertension, + registered_patients.user_id, + registered_patients.visit_id, + registered_patients.visit_type, + registered_patients.visited_at, + registered_patients.day_of_year + FROM (registered_patients + JOIN public.medical_histories mh ON ((registered_patients.patient_id = mh.patient_id))) + ), daily_registered_patients AS ( + SELECT DISTINCT ON (registered_patients_with_medical_histories.facility_id, (date(((registered_patients_with_medical_histories.visited_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))) registered_patients_with_medical_histories.facility_id, + date(((registered_patients_with_medical_histories.visited_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) AS visit_date, + registered_patients_with_medical_histories.day_of_year, + count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) OR (registered_patients_with_medical_histories.diabetes = 'yes'::text))) AS daily_registrations_htn_or_dm, + count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text))) AS daily_registrations_htn_and_dm, + count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'female'::public.gender_enum))) AS daily_registrations_htn_and_dm_female, + count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'male'::public.gender_enum))) AS daily_registrations_htn_and_dm_male, + count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'transgender'::public.gender_enum))) AS daily_registrations_htn_and_dm_transgender, + (count(*) FILTER (WHERE (registered_patients_with_medical_histories.hypertension = 'yes'::text)) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text)))) AS daily_registrations_htn_only, + (count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'female'::public.gender_enum))) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'female'::public.gender_enum)))) AS daily_registrations_htn_only_female, + (count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'male'::public.gender_enum))) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'male'::public.gender_enum)))) AS daily_registrations_htn_only_male, + (count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'transgender'::public.gender_enum))) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'transgender'::public.gender_enum)))) AS daily_registrations_htn_only_transgender, + (count(*) FILTER (WHERE (registered_patients_with_medical_histories.diabetes = 'yes'::text)) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text)))) AS daily_registrations_dm_only, + (count(*) FILTER (WHERE ((registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'female'::public.gender_enum))) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'female'::public.gender_enum)))) AS daily_registrations_dm_only_female, + (count(*) FILTER (WHERE ((registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'male'::public.gender_enum))) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'male'::public.gender_enum)))) AS daily_registrations_dm_only_male, + (count(*) FILTER (WHERE ((registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'transgender'::public.gender_enum))) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'transgender'::public.gender_enum)))) AS daily_registrations_dm_only_transgender + FROM registered_patients_with_medical_histories + GROUP BY registered_patients_with_medical_histories.facility_id, (date(((registered_patients_with_medical_histories.visited_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting)))), registered_patients_with_medical_histories.day_of_year + ), daily_follow_ups AS ( + SELECT DISTINCT ON (all_follow_ups_with_medical_histories.facility_id, (date(((all_follow_ups_with_medical_histories.visited_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))) all_follow_ups_with_medical_histories.facility_id, + date(((all_follow_ups_with_medical_histories.visited_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) AS visit_date, + all_follow_ups_with_medical_histories.day_of_year, + count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) OR (all_follow_ups_with_medical_histories.diabetes = 'yes'::text))) AS daily_follow_ups_htn_or_dm, + count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text))) AS daily_follow_ups_htn_and_dm, + count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'female'::public.gender_enum))) AS daily_follow_ups_htn_and_dm_female, + count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'male'::public.gender_enum))) AS daily_follow_ups_htn_and_dm_male, + count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'transgender'::public.gender_enum))) AS daily_follow_ups_htn_and_dm_transgender, + (count(*) FILTER (WHERE (all_follow_ups_with_medical_histories.hypertension = 'yes'::text)) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text)))) AS daily_follow_ups_htn_only, + (count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'female'::public.gender_enum))) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'female'::public.gender_enum)))) AS daily_follow_ups_htn_only_female, + (count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'male'::public.gender_enum))) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'male'::public.gender_enum)))) AS daily_follow_ups_htn_only_male, + (count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'transgender'::public.gender_enum))) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'transgender'::public.gender_enum)))) AS daily_follow_ups_htn_only_transgender, + (count(*) FILTER (WHERE (all_follow_ups_with_medical_histories.diabetes = 'yes'::text)) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text)))) AS daily_follow_ups_dm_only, + (count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'female'::public.gender_enum))) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'female'::public.gender_enum)))) AS daily_follow_ups_dm_only_female, + (count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'male'::public.gender_enum))) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'male'::public.gender_enum)))) AS daily_follow_ups_dm_only_male, + (count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'transgender'::public.gender_enum))) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'transgender'::public.gender_enum)))) AS daily_follow_ups_dm_only_transgender + FROM all_follow_ups_with_medical_histories + GROUP BY all_follow_ups_with_medical_histories.facility_id, (date(((all_follow_ups_with_medical_histories.visited_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting)))), all_follow_ups_with_medical_histories.day_of_year + ), last_30_days AS ( + SELECT (generate_series((CURRENT_TIMESTAMP - '30 days'::interval), CURRENT_TIMESTAMP, '1 day'::interval))::date AS date + ) + SELECT rf.facility_region_slug, + rf.facility_id, + rf.facility_region_id, + rf.block_region_id, + rf.district_region_id, + rf.state_region_id, + last_30_days.date AS visit_date, + (EXTRACT(doy FROM ((last_30_days.date AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year, + COALESCE(daily_registered_patients.daily_registrations_htn_or_dm, (0)::bigint) AS daily_registrations_htn_or_dm, + COALESCE(daily_registered_patients.daily_registrations_htn_only, (0)::bigint) AS daily_registrations_htn_only, + COALESCE(daily_registered_patients.daily_registrations_htn_only_male, (0)::bigint) AS daily_registrations_htn_only_male, + COALESCE(daily_registered_patients.daily_registrations_htn_only_female, (0)::bigint) AS daily_registrations_htn_only_female, + COALESCE(daily_registered_patients.daily_registrations_htn_only_transgender, (0)::bigint) AS daily_registrations_htn_only_transgender, + COALESCE(daily_registered_patients.daily_registrations_dm_only, (0)::bigint) AS daily_registrations_dm_only, + COALESCE(daily_registered_patients.daily_registrations_dm_only_male, (0)::bigint) AS daily_registrations_dm_only_male, + COALESCE(daily_registered_patients.daily_registrations_dm_only_female, (0)::bigint) AS daily_registrations_dm_only_female, + COALESCE(daily_registered_patients.daily_registrations_dm_only_transgender, (0)::bigint) AS daily_registrations_dm_only_transgender, + COALESCE(daily_registered_patients.daily_registrations_htn_and_dm, (0)::bigint) AS daily_registrations_htn_and_dm, + COALESCE(daily_registered_patients.daily_registrations_htn_and_dm_male, (0)::bigint) AS daily_registrations_htn_and_dm_male, + COALESCE(daily_registered_patients.daily_registrations_htn_and_dm_female, (0)::bigint) AS daily_registrations_htn_and_dm_female, + COALESCE(daily_registered_patients.daily_registrations_htn_and_dm_transgender, (0)::bigint) AS daily_registrations_htn_and_dm_transgender, + COALESCE(daily_follow_ups.daily_follow_ups_htn_or_dm, (0)::bigint) AS daily_follow_ups_htn_or_dm, + COALESCE(daily_follow_ups.daily_follow_ups_htn_only, (0)::bigint) AS daily_follow_ups_htn_only, + COALESCE(daily_follow_ups.daily_follow_ups_htn_only_female, (0)::bigint) AS daily_follow_ups_htn_only_female, + COALESCE(daily_follow_ups.daily_follow_ups_htn_only_male, (0)::bigint) AS daily_follow_ups_htn_only_male, + COALESCE(daily_follow_ups.daily_follow_ups_htn_only_transgender, (0)::bigint) AS daily_follow_ups_htn_only_transgender, + COALESCE(daily_follow_ups.daily_follow_ups_dm_only, (0)::bigint) AS daily_follow_ups_dm_only, + COALESCE(daily_follow_ups.daily_follow_ups_dm_only_female, (0)::bigint) AS daily_follow_ups_dm_only_female, + COALESCE(daily_follow_ups.daily_follow_ups_dm_only_male, (0)::bigint) AS daily_follow_ups_dm_only_male, + COALESCE(daily_follow_ups.daily_follow_ups_dm_only_transgender, (0)::bigint) AS daily_follow_ups_dm_only_transgender, + COALESCE(daily_follow_ups.daily_follow_ups_htn_and_dm, (0)::bigint) AS daily_follow_ups_htn_and_dm, + COALESCE(daily_follow_ups.daily_follow_ups_htn_and_dm_male, (0)::bigint) AS daily_follow_ups_htn_and_dm_male, + COALESCE(daily_follow_ups.daily_follow_ups_htn_and_dm_female, (0)::bigint) AS daily_follow_ups_htn_and_dm_female, + COALESCE(daily_follow_ups.daily_follow_ups_htn_and_dm_transgender, (0)::bigint) AS daily_follow_ups_htn_and_dm_transgender + FROM (((public.reporting_facilities rf + JOIN last_30_days ON (true)) + LEFT JOIN daily_registered_patients ON (((daily_registered_patients.visit_date = last_30_days.date) AND (daily_registered_patients.facility_id = rf.facility_id)))) + LEFT JOIN daily_follow_ups ON (((daily_follow_ups.visit_date = last_30_days.date) AND (daily_follow_ups.facility_id = rf.facility_id)))) + ORDER BY last_30_days.date DESC + WITH NO DATA; + SQL + + add_index :reporting_facility_daily_follow_ups_and_registrations, [:facility_id, :visit_date], name: "fd_far_facility_id_visit_date", unique: true + add_index :reporting_facility_daily_follow_ups_and_registrations, [:facility_region_id, :visit_date], name: "index_df_facility_region_id_visit_date", unique: true + end + + def down + drop_view :reporting_facility_daily_follow_ups_and_registrations, materialized: true + execute <<~SQL + CREATE MATERIALIZED VIEW public.reporting_facility_daily_follow_ups_and_registrations AS + WITH follow_up_blood_pressures AS ( + SELECT DISTINCT ON (((EXTRACT(doy FROM ((bp.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), bp.facility_id, p.id) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + bp.id AS visit_id, + 'BloodPressure'::text AS visit_type, + bp.facility_id, + bp.user_id, + bp.recorded_at AS visited_at, + (EXTRACT(doy FROM ((bp.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year + FROM (public.patients p + JOIN public.blood_pressures bp ON (((p.id = bp.patient_id) AND (date_trunc('day'::text, ((bp.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) + WHERE ((p.deleted_at IS NULL) AND (bp.recorded_at > (CURRENT_TIMESTAMP - '30 days'::interval))) + ), follow_up_blood_sugars AS ( + SELECT DISTINCT ON (((EXTRACT(doy FROM ((bs.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), bs.facility_id, p.id) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + bs.id AS visit_id, + 'BloodSugar'::text AS visit_type, + bs.facility_id, + bs.user_id, + bs.recorded_at AS visited_at, + (EXTRACT(doy FROM ((bs.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year + FROM (public.patients p + JOIN public.blood_sugars bs ON (((p.id = bs.patient_id) AND (date_trunc('day'::text, ((bs.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) + WHERE ((p.deleted_at IS NULL) AND (bs.recorded_at > (CURRENT_TIMESTAMP - '30 days'::interval))) + ), follow_up_prescription_drugs AS ( + SELECT DISTINCT ON (((EXTRACT(doy FROM ((pd.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), pd.facility_id, p.id) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + pd.id AS visit_id, + 'PrescriptionDrug'::text AS visit_type, + pd.facility_id, + pd.user_id, + pd.device_created_at AS visited_at, + (EXTRACT(doy FROM ((pd.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year + FROM (public.patients p + JOIN public.prescription_drugs pd ON (((p.id = pd.patient_id) AND (date_trunc('day'::text, ((pd.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) + WHERE ((p.deleted_at IS NULL) AND (pd.device_created_at > (CURRENT_TIMESTAMP - '30 days'::interval))) + ), follow_up_appointments AS ( + SELECT DISTINCT ON (((EXTRACT(doy FROM ((app.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), app.creation_facility_id, p.id) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + app.id AS visit_id, + 'Appointment'::text AS visit_type, + app.creation_facility_id AS facility_id, + app.user_id, + app.device_created_at AS visited_at, + (EXTRACT(doy FROM ((app.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year + FROM (public.patients p + JOIN public.appointments app ON (((p.id = app.patient_id) AND (date_trunc('day'::text, ((app.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) + WHERE ((p.deleted_at IS NULL) AND (app.device_created_at > (CURRENT_TIMESTAMP - '30 days'::interval))) + ), registered_patients AS ( + SELECT DISTINCT ON (((EXTRACT(doy FROM ((p.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), p.registration_facility_id, p.id) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + p.id AS visit_id, + 'Registration'::text AS visit_type, + p.registration_facility_id AS facility_id, + p.registration_user_id AS user_id, + p.recorded_at AS visited_at, + (EXTRACT(doy FROM ((p.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year + FROM public.patients p + WHERE ((p.deleted_at IS NULL) AND (p.recorded_at > (CURRENT_TIMESTAMP - '30 days'::interval))) + ), all_follow_ups AS ( + SELECT follow_up_blood_pressures.patient_id, + follow_up_blood_pressures.patient_gender, + follow_up_blood_pressures.visit_id, + follow_up_blood_pressures.visit_type, + follow_up_blood_pressures.facility_id, + follow_up_blood_pressures.user_id, + follow_up_blood_pressures.visited_at, + follow_up_blood_pressures.day_of_year + FROM follow_up_blood_pressures + UNION + SELECT follow_up_blood_sugars.patient_id, + follow_up_blood_sugars.patient_gender, + follow_up_blood_sugars.visit_id, + follow_up_blood_sugars.visit_type, + follow_up_blood_sugars.facility_id, + follow_up_blood_sugars.user_id, + follow_up_blood_sugars.visited_at, + follow_up_blood_sugars.day_of_year + FROM follow_up_blood_sugars + UNION + SELECT follow_up_prescription_drugs.patient_id, + follow_up_prescription_drugs.patient_gender, + follow_up_prescription_drugs.visit_id, + follow_up_prescription_drugs.visit_type, + follow_up_prescription_drugs.facility_id, + follow_up_prescription_drugs.user_id, + follow_up_prescription_drugs.visited_at, + follow_up_prescription_drugs.day_of_year + FROM follow_up_prescription_drugs + UNION + SELECT follow_up_appointments.patient_id, + follow_up_appointments.patient_gender, + follow_up_appointments.visit_id, + follow_up_appointments.visit_type, + follow_up_appointments.facility_id, + follow_up_appointments.user_id, + follow_up_appointments.visited_at, + follow_up_appointments.day_of_year + FROM follow_up_appointments + ), all_follow_ups_with_medical_histories AS ( + SELECT DISTINCT ON (all_follow_ups.day_of_year, all_follow_ups.facility_id, all_follow_ups.patient_id) all_follow_ups.patient_id, + all_follow_ups.patient_gender, + all_follow_ups.facility_id, + mh.diabetes, + mh.hypertension, + all_follow_ups.user_id, + all_follow_ups.visit_id, + all_follow_ups.visit_type, + all_follow_ups.visited_at, + all_follow_ups.day_of_year + FROM (all_follow_ups + JOIN public.medical_histories mh ON ((all_follow_ups.patient_id = mh.patient_id))) + ), registered_patients_with_medical_histories AS ( + SELECT DISTINCT ON (registered_patients.day_of_year, registered_patients.facility_id, registered_patients.patient_id) registered_patients.patient_id, + registered_patients.patient_gender, + registered_patients.facility_id, + mh.diabetes, + mh.hypertension, + registered_patients.user_id, + registered_patients.visit_id, + registered_patients.visit_type, + registered_patients.visited_at, + registered_patients.day_of_year + FROM (registered_patients + JOIN public.medical_histories mh ON ((registered_patients.patient_id = mh.patient_id))) + ), daily_registered_patients AS ( + SELECT DISTINCT ON (registered_patients_with_medical_histories.facility_id, (date(((registered_patients_with_medical_histories.visited_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))) registered_patients_with_medical_histories.facility_id, + date(((registered_patients_with_medical_histories.visited_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) AS visit_date, + registered_patients_with_medical_histories.day_of_year, + count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) OR (registered_patients_with_medical_histories.diabetes = 'yes'::text))) AS daily_registrations_htn_or_dm, + count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text))) AS daily_registrations_htn_and_dm, + count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'female'::public.gender_enum))) AS daily_registrations_htn_and_dm_female, + count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'male'::public.gender_enum))) AS daily_registrations_htn_and_dm_male, + count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'transgender'::public.gender_enum))) AS daily_registrations_htn_and_dm_transgender, + (count(*) FILTER (WHERE (registered_patients_with_medical_histories.hypertension = 'yes'::text)) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text)))) AS daily_registrations_htn_only, + (count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'female'::public.gender_enum))) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'female'::public.gender_enum)))) AS daily_registrations_htn_only_female, + (count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'male'::public.gender_enum))) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'male'::public.gender_enum)))) AS daily_registrations_htn_only_male, + (count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'transgender'::public.gender_enum))) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'transgender'::public.gender_enum)))) AS daily_registrations_htn_only_transgender, + (count(*) FILTER (WHERE (registered_patients_with_medical_histories.diabetes = 'yes'::text)) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text)))) AS daily_registrations_dm_only, + (count(*) FILTER (WHERE ((registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'female'::public.gender_enum))) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'female'::public.gender_enum)))) AS daily_registrations_dm_only_female, + (count(*) FILTER (WHERE ((registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'male'::public.gender_enum))) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'male'::public.gender_enum)))) AS daily_registrations_dm_only_male, + (count(*) FILTER (WHERE ((registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'transgender'::public.gender_enum))) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'transgender'::public.gender_enum)))) AS daily_registrations_dm_only_transgender + FROM registered_patients_with_medical_histories + GROUP BY registered_patients_with_medical_histories.facility_id, (date(((registered_patients_with_medical_histories.visited_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting)))), registered_patients_with_medical_histories.day_of_year + ), daily_follow_ups AS ( + SELECT DISTINCT ON (all_follow_ups_with_medical_histories.facility_id, (date(((all_follow_ups_with_medical_histories.visited_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))) all_follow_ups_with_medical_histories.facility_id, + date(((all_follow_ups_with_medical_histories.visited_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) AS visit_date, + all_follow_ups_with_medical_histories.day_of_year, + count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) OR (all_follow_ups_with_medical_histories.diabetes = 'yes'::text))) AS daily_follow_ups_htn_or_dm, + count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text))) AS daily_follow_ups_htn_and_dm, + count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'female'::public.gender_enum))) AS daily_follow_ups_htn_and_dm_female, + count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'male'::public.gender_enum))) AS daily_follow_ups_htn_and_dm_male, + count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'transgender'::public.gender_enum))) AS daily_follow_ups_htn_and_dm_transgender, + (count(*) FILTER (WHERE (all_follow_ups_with_medical_histories.hypertension = 'yes'::text)) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text)))) AS daily_follow_ups_htn_only, + (count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'female'::public.gender_enum))) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'female'::public.gender_enum)))) AS daily_follow_ups_htn_only_female, + (count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'male'::public.gender_enum))) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'male'::public.gender_enum)))) AS daily_follow_ups_htn_only_male, + (count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'transgender'::public.gender_enum))) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'transgender'::public.gender_enum)))) AS daily_follow_ups_htn_only_transgender, + (count(*) FILTER (WHERE (all_follow_ups_with_medical_histories.diabetes = 'yes'::text)) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text)))) AS daily_follow_ups_dm_only, + (count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'female'::public.gender_enum))) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'female'::public.gender_enum)))) AS daily_follow_ups_dm_only_female, + (count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'male'::public.gender_enum))) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'male'::public.gender_enum)))) AS daily_follow_ups_dm_only_male, + (count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'transgender'::public.gender_enum))) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'transgender'::public.gender_enum)))) AS daily_follow_ups_dm_only_transgender + FROM all_follow_ups_with_medical_histories + GROUP BY all_follow_ups_with_medical_histories.facility_id, (date(((all_follow_ups_with_medical_histories.visited_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting)))), all_follow_ups_with_medical_histories.day_of_year + ), last_30_days AS ( + SELECT (generate_series((CURRENT_TIMESTAMP - '30 days'::interval), CURRENT_TIMESTAMP, '1 day'::interval))::date AS date + ) + SELECT rf.facility_region_slug, + rf.facility_id, + rf.facility_region_id, + rf.block_region_id, + rf.district_region_id, + rf.state_region_id, + last_30_days.date AS visit_date, + (EXTRACT(doy FROM ((last_30_days.date AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year, + COALESCE(daily_registered_patients.daily_registrations_htn_or_dm, (0)::bigint) AS daily_registrations_htn_or_dm, + COALESCE(daily_registered_patients.daily_registrations_htn_only, (0)::bigint) AS daily_registrations_htn_only, + COALESCE(daily_registered_patients.daily_registrations_htn_only_male, (0)::bigint) AS daily_registrations_htn_only_male, + COALESCE(daily_registered_patients.daily_registrations_htn_only_female, (0)::bigint) AS daily_registrations_htn_only_female, + COALESCE(daily_registered_patients.daily_registrations_htn_only_transgender, (0)::bigint) AS daily_registrations_htn_only_transgender, + COALESCE(daily_registered_patients.daily_registrations_dm_only, (0)::bigint) AS daily_registrations_dm_only, + COALESCE(daily_registered_patients.daily_registrations_dm_only_male, (0)::bigint) AS daily_registrations_dm_only_male, + COALESCE(daily_registered_patients.daily_registrations_dm_only_female, (0)::bigint) AS daily_registrations_dm_only_female, + COALESCE(daily_registered_patients.daily_registrations_dm_only_transgender, (0)::bigint) AS daily_registrations_dm_only_transgender, + COALESCE(daily_registered_patients.daily_registrations_htn_and_dm, (0)::bigint) AS daily_registrations_htn_and_dm, + COALESCE(daily_registered_patients.daily_registrations_htn_and_dm_male, (0)::bigint) AS daily_registrations_htn_and_dm_male, + COALESCE(daily_registered_patients.daily_registrations_htn_and_dm_female, (0)::bigint) AS daily_registrations_htn_and_dm_female, + COALESCE(daily_registered_patients.daily_registrations_htn_and_dm_transgender, (0)::bigint) AS daily_registrations_htn_and_dm_transgender, + COALESCE(daily_follow_ups.daily_follow_ups_htn_or_dm, (0)::bigint) AS daily_follow_ups_htn_or_dm, + COALESCE(daily_follow_ups.daily_follow_ups_htn_only, (0)::bigint) AS daily_follow_ups_htn_only, + COALESCE(daily_follow_ups.daily_follow_ups_htn_only_female, (0)::bigint) AS daily_follow_ups_htn_only_female, + COALESCE(daily_follow_ups.daily_follow_ups_htn_only_male, (0)::bigint) AS daily_follow_ups_htn_only_male, + COALESCE(daily_follow_ups.daily_follow_ups_htn_only_transgender, (0)::bigint) AS daily_follow_ups_htn_only_transgender, + COALESCE(daily_follow_ups.daily_follow_ups_dm_only, (0)::bigint) AS daily_follow_ups_dm_only, + COALESCE(daily_follow_ups.daily_follow_ups_dm_only_female, (0)::bigint) AS daily_follow_ups_dm_only_female, + COALESCE(daily_follow_ups.daily_follow_ups_dm_only_male, (0)::bigint) AS daily_follow_ups_dm_only_male, + COALESCE(daily_follow_ups.daily_follow_ups_dm_only_transgender, (0)::bigint) AS daily_follow_ups_dm_only_transgender, + COALESCE(daily_follow_ups.daily_follow_ups_htn_and_dm, (0)::bigint) AS daily_follow_ups_htn_and_dm, + COALESCE(daily_follow_ups.daily_follow_ups_htn_and_dm_male, (0)::bigint) AS daily_follow_ups_htn_and_dm_male, + COALESCE(daily_follow_ups.daily_follow_ups_htn_and_dm_female, (0)::bigint) AS daily_follow_ups_htn_and_dm_female, + COALESCE(daily_follow_ups.daily_follow_ups_htn_and_dm_transgender, (0)::bigint) AS daily_follow_ups_htn_and_dm_transgender + FROM (((public.reporting_facilities rf + JOIN last_30_days ON (true)) + LEFT JOIN daily_registered_patients ON (((daily_registered_patients.visit_date = last_30_days.date) AND (daily_registered_patients.facility_id = rf.facility_id)))) + LEFT JOIN daily_follow_ups ON (((daily_follow_ups.visit_date = last_30_days.date) AND (daily_follow_ups.facility_id = rf.facility_id)))) + ORDER BY last_30_days.date DESC + WITH NO DATA; + SQL + + add_index :reporting_facility_daily_follow_ups_and_registrations, [:facility_id, :visit_date], name: "fd_far_facility_id_visit_date", unique: true + add_index :reporting_facility_daily_follow_ups_and_registrations, [:facility_region_id, :visit_date], name: "index_df_facility_region_id_visit_date", unique: true + end +end diff --git a/db/migrate/20251211154907_update_materialized_patient_summaries_to_version4.rb b/db/migrate/20251211154907_update_materialized_patient_summaries_to_version4.rb new file mode 100644 index 0000000000..111db7cad0 --- /dev/null +++ b/db/migrate/20251211154907_update_materialized_patient_summaries_to_version4.rb @@ -0,0 +1,919 @@ +class UpdateMaterializedPatientSummariesToVersion4 < ActiveRecord::Migration[6.1] + def up + drop_view :materialized_patient_summaries, materialized: true + execute <<~SQL + CREATE MATERIALIZED VIEW public.materialized_patient_summaries AS + WITH latest_bp_passport AS ( + SELECT DISTINCT ON (patient_business_identifiers.patient_id) patient_business_identifiers.id, + patient_business_identifiers.identifier, + patient_business_identifiers.identifier_type, + patient_business_identifiers.patient_id, + patient_business_identifiers.metadata_version, + patient_business_identifiers.metadata, + patient_business_identifiers.device_created_at, + patient_business_identifiers.device_updated_at, + patient_business_identifiers.deleted_at, + patient_business_identifiers.created_at, + patient_business_identifiers.updated_at + FROM public.patient_business_identifiers + WHERE (((patient_business_identifiers.identifier_type)::text = 'simple_bp_passport'::text) AND (patient_business_identifiers.deleted_at IS NULL)) + ORDER BY patient_business_identifiers.patient_id, patient_business_identifiers.device_created_at DESC + ), latest_phone_number AS ( + SELECT DISTINCT ON (patient_phone_numbers.patient_id) patient_phone_numbers.id, + patient_phone_numbers.number, + patient_phone_numbers.phone_type, + patient_phone_numbers.active, + patient_phone_numbers.created_at, + patient_phone_numbers.updated_at, + patient_phone_numbers.patient_id, + patient_phone_numbers.device_created_at, + patient_phone_numbers.device_updated_at, + patient_phone_numbers.deleted_at, + patient_phone_numbers.dnd_status + FROM public.patient_phone_numbers + WHERE (patient_phone_numbers.deleted_at IS NULL) + ORDER BY patient_phone_numbers.patient_id, patient_phone_numbers.device_created_at DESC + ), latest_medical_history AS ( + SELECT DISTINCT ON (medical_histories.patient_id) medical_histories.id, + medical_histories.patient_id, + medical_histories.prior_heart_attack_boolean, + medical_histories.prior_stroke_boolean, + medical_histories.chronic_kidney_disease_boolean, + medical_histories.receiving_treatment_for_hypertension_boolean, + medical_histories.diabetes_boolean, + medical_histories.device_created_at, + medical_histories.device_updated_at, + medical_histories.created_at, + medical_histories.updated_at, + medical_histories.diagnosed_with_hypertension_boolean, + medical_histories.prior_heart_attack, + medical_histories.prior_stroke, + medical_histories.chronic_kidney_disease, + medical_histories.receiving_treatment_for_hypertension, + medical_histories.diabetes, + medical_histories.diagnosed_with_hypertension, + medical_histories.deleted_at, + medical_histories.user_id, + medical_histories.hypertension, + medical_histories.receiving_treatment_for_diabetes, + medical_histories.smoking, + medical_histories.cholesterol + FROM public.medical_histories + WHERE (medical_histories.deleted_at IS NULL) + ), ranked_prescription_drugs AS ( + SELECT bp.id AS bp_id, + array_agg(ARRAY[prescription_drugs.name, prescription_drugs.dosage] ORDER BY prescription_drugs.is_protocol_drug DESC, prescription_drugs.name, prescription_drugs.device_created_at DESC) AS blood_pressure_drugs, + array_agg((((prescription_drugs.name)::text || '-'::text) || (prescription_drugs.dosage)::text) ORDER BY prescription_drugs.is_protocol_drug DESC, prescription_drugs.name, prescription_drugs.device_created_at DESC) AS drug_strings + FROM (public.blood_pressures bp + JOIN public.prescription_drugs ON (((prescription_drugs.patient_id = bp.patient_id) AND (date(prescription_drugs.device_created_at) <= date(bp.recorded_at)) AND ((prescription_drugs.is_deleted IS FALSE) OR ((prescription_drugs.is_deleted IS TRUE) AND (date(prescription_drugs.device_updated_at) > date(bp.recorded_at))))))) + WHERE ((bp.deleted_at IS NULL) AND (prescription_drugs.deleted_at IS NULL)) + GROUP BY bp.id + ), blood_pressure_follow_up AS ( + SELECT DISTINCT ON (bp.patient_id, (date(bp.recorded_at))) bp.id AS bp_id, + appointments.id, + appointments.patient_id, + appointments.facility_id, + appointments.scheduled_date, + appointments.status, + appointments.cancel_reason, + appointments.device_created_at, + appointments.device_updated_at, + appointments.created_at, + appointments.updated_at, + appointments.remind_on, + appointments.agreed_to_visit, + appointments.deleted_at, + appointments.appointment_type, + appointments.user_id, + appointments.creation_facility_id + FROM (public.blood_pressures bp + JOIN public.appointments ON (((appointments.patient_id = bp.patient_id) AND (date(appointments.device_created_at) = date(bp.recorded_at))))) + ORDER BY bp.patient_id, (date(bp.recorded_at)), appointments.device_created_at DESC + ), blood_sugar_follow_up AS ( + SELECT DISTINCT ON (bs.patient_id, (date(bs.recorded_at))) bs.id AS bs_id, + appointments.id, + appointments.patient_id, + appointments.facility_id, + appointments.scheduled_date, + appointments.status, + appointments.cancel_reason, + appointments.device_created_at, + appointments.device_updated_at, + appointments.created_at, + appointments.updated_at, + appointments.remind_on, + appointments.agreed_to_visit, + appointments.deleted_at, + appointments.appointment_type, + appointments.user_id, + appointments.creation_facility_id + FROM (public.blood_sugars bs + JOIN public.appointments ON (((appointments.patient_id = bs.patient_id) AND (date(appointments.device_created_at) = date(bs.recorded_at))))) + ORDER BY bs.patient_id, (date(bs.recorded_at)), appointments.device_created_at DESC + ), ranked_blood_pressures AS ( + SELECT bp.id, + bp.patient_id, + bp.recorded_at, + bp.systolic, + bp.diastolic, + f.name AS facility_name, + f.facility_type, + f.district, + f.state, + follow_up_facility.name AS follow_up_facility_name, + a.scheduled_date AS follow_up_date, + (GREATEST((0)::double precision, date_part('day'::text, ((a.scheduled_date)::timestamp without time zone - date_trunc('day'::text, a.device_created_at)))))::integer AS follow_up_days, + bp_drugs.blood_pressure_drugs[1][1] AS prescription_drug_1_name, + bp_drugs.blood_pressure_drugs[1][2] AS prescription_drug_1_dosage, + bp_drugs.blood_pressure_drugs[2][1] AS prescription_drug_2_name, + bp_drugs.blood_pressure_drugs[2][2] AS prescription_drug_2_dosage, + bp_drugs.blood_pressure_drugs[3][1] AS prescription_drug_3_name, + bp_drugs.blood_pressure_drugs[3][2] AS prescription_drug_3_dosage, + bp_drugs.blood_pressure_drugs[4][1] AS prescription_drug_4_name, + bp_drugs.blood_pressure_drugs[4][2] AS prescription_drug_4_dosage, + bp_drugs.blood_pressure_drugs[5][1] AS prescription_drug_5_name, + bp_drugs.blood_pressure_drugs[5][2] AS prescription_drug_5_dosage, + ( SELECT string_agg(value.value, ', '::text) AS string_agg + FROM unnest(bp_drugs.drug_strings[6:]) value(value)) AS other_prescription_drugs, + ( SELECT string_agg(value.value, ', '::text) AS string_agg + FROM unnest(bp_drugs.drug_strings) value(value)) AS all_prescription_drugs, + rank() OVER (PARTITION BY bp.patient_id ORDER BY bp.recorded_at DESC, bp.id) AS rank + FROM ((((public.blood_pressures bp + LEFT JOIN public.facilities f ON ((bp.facility_id = f.id))) + LEFT JOIN ranked_prescription_drugs bp_drugs ON ((bp.id = bp_drugs.bp_id))) + LEFT JOIN blood_pressure_follow_up a ON ((a.bp_id = bp.id))) + LEFT JOIN public.facilities follow_up_facility ON ((follow_up_facility.id = a.facility_id))) + WHERE ((bp.deleted_at IS NULL) AND (a.deleted_at IS NULL)) + ), latest_blood_pressures AS ( + SELECT latest_blood_pressure_1.patient_id, + latest_blood_pressure_1.id AS latest_blood_pressure_1_id, + latest_blood_pressure_1.recorded_at AS latest_blood_pressure_1_recorded_at, + latest_blood_pressure_1.systolic AS latest_blood_pressure_1_systolic, + latest_blood_pressure_1.diastolic AS latest_blood_pressure_1_diastolic, + latest_blood_pressure_1.facility_name AS latest_blood_pressure_1_facility_name, + latest_blood_pressure_1.facility_type AS latest_blood_pressure_1_facility_type, + latest_blood_pressure_1.district AS latest_blood_pressure_1_district, + latest_blood_pressure_1.state AS latest_blood_pressure_1_state, + latest_blood_pressure_1.follow_up_facility_name AS latest_blood_pressure_1_follow_up_facility_name, + latest_blood_pressure_1.follow_up_date AS latest_blood_pressure_1_follow_up_date, + latest_blood_pressure_1.follow_up_days AS latest_blood_pressure_1_follow_up_days, + (latest_blood_pressure_1.all_prescription_drugs <> latest_blood_pressure_2.all_prescription_drugs) AS latest_blood_pressure_1_medication_updated, + latest_blood_pressure_1.prescription_drug_1_name AS latest_blood_pressure_1_prescription_drug_1_name, + latest_blood_pressure_1.prescription_drug_1_dosage AS latest_blood_pressure_1_prescription_drug_1_dosage, + latest_blood_pressure_1.prescription_drug_2_name AS latest_blood_pressure_1_prescription_drug_2_name, + latest_blood_pressure_1.prescription_drug_2_dosage AS latest_blood_pressure_1_prescription_drug_2_dosage, + latest_blood_pressure_1.prescription_drug_3_name AS latest_blood_pressure_1_prescription_drug_3_name, + latest_blood_pressure_1.prescription_drug_3_dosage AS latest_blood_pressure_1_prescription_drug_3_dosage, + latest_blood_pressure_1.prescription_drug_4_name AS latest_blood_pressure_1_prescription_drug_4_name, + latest_blood_pressure_1.prescription_drug_4_dosage AS latest_blood_pressure_1_prescription_drug_4_dosage, + latest_blood_pressure_1.prescription_drug_5_name AS latest_blood_pressure_1_prescription_drug_5_name, + latest_blood_pressure_1.prescription_drug_5_dosage AS latest_blood_pressure_1_prescription_drug_5_dosage, + latest_blood_pressure_1.other_prescription_drugs AS latest_blood_pressure_1_other_prescription_drugs, + latest_blood_pressure_2.id AS latest_blood_pressure_2_id, + latest_blood_pressure_2.recorded_at AS latest_blood_pressure_2_recorded_at, + latest_blood_pressure_2.systolic AS latest_blood_pressure_2_systolic, + latest_blood_pressure_2.diastolic AS latest_blood_pressure_2_diastolic, + latest_blood_pressure_2.facility_name AS latest_blood_pressure_2_facility_name, + latest_blood_pressure_2.facility_type AS latest_blood_pressure_2_facility_type, + latest_blood_pressure_2.district AS latest_blood_pressure_2_district, + latest_blood_pressure_2.state AS latest_blood_pressure_2_state, + latest_blood_pressure_2.follow_up_facility_name AS latest_blood_pressure_2_follow_up_facility_name, + latest_blood_pressure_2.follow_up_date AS latest_blood_pressure_2_follow_up_date, + latest_blood_pressure_2.follow_up_days AS latest_blood_pressure_2_follow_up_days, + (latest_blood_pressure_2.all_prescription_drugs <> latest_blood_pressure_3.all_prescription_drugs) AS latest_blood_pressure_2_medication_updated, + latest_blood_pressure_2.prescription_drug_1_name AS latest_blood_pressure_2_prescription_drug_1_name, + latest_blood_pressure_2.prescription_drug_1_dosage AS latest_blood_pressure_2_prescription_drug_1_dosage, + latest_blood_pressure_2.prescription_drug_2_name AS latest_blood_pressure_2_prescription_drug_2_name, + latest_blood_pressure_2.prescription_drug_2_dosage AS latest_blood_pressure_2_prescription_drug_2_dosage, + latest_blood_pressure_2.prescription_drug_3_name AS latest_blood_pressure_2_prescription_drug_3_name, + latest_blood_pressure_2.prescription_drug_3_dosage AS latest_blood_pressure_2_prescription_drug_3_dosage, + latest_blood_pressure_2.prescription_drug_4_name AS latest_blood_pressure_2_prescription_drug_4_name, + latest_blood_pressure_2.prescription_drug_4_dosage AS latest_blood_pressure_2_prescription_drug_4_dosage, + latest_blood_pressure_2.prescription_drug_5_name AS latest_blood_pressure_2_prescription_drug_5_name, + latest_blood_pressure_2.prescription_drug_5_dosage AS latest_blood_pressure_2_prescription_drug_5_dosage, + latest_blood_pressure_2.other_prescription_drugs AS latest_blood_pressure_2_other_prescription_drugs, + latest_blood_pressure_3.id AS latest_blood_pressure_3_id, + latest_blood_pressure_3.recorded_at AS latest_blood_pressure_3_recorded_at, + latest_blood_pressure_3.systolic AS latest_blood_pressure_3_systolic, + latest_blood_pressure_3.diastolic AS latest_blood_pressure_3_diastolic, + latest_blood_pressure_3.facility_name AS latest_blood_pressure_3_facility_name, + latest_blood_pressure_3.facility_type AS latest_blood_pressure_3_facility_type, + latest_blood_pressure_3.district AS latest_blood_pressure_3_district, + latest_blood_pressure_3.state AS latest_blood_pressure_3_state, + latest_blood_pressure_3.follow_up_facility_name AS latest_blood_pressure_3_follow_up_facility_name, + latest_blood_pressure_3.follow_up_date AS latest_blood_pressure_3_follow_up_date, + latest_blood_pressure_3.follow_up_days AS latest_blood_pressure_3_follow_up_days, + (latest_blood_pressure_3.all_prescription_drugs <> latest_blood_pressure_4.all_prescription_drugs) AS latest_blood_pressure_3_medication_updated, + latest_blood_pressure_3.prescription_drug_1_name AS latest_blood_pressure_3_prescription_drug_1_name, + latest_blood_pressure_3.prescription_drug_1_dosage AS latest_blood_pressure_3_prescription_drug_1_dosage, + latest_blood_pressure_3.prescription_drug_2_name AS latest_blood_pressure_3_prescription_drug_2_name, + latest_blood_pressure_3.prescription_drug_2_dosage AS latest_blood_pressure_3_prescription_drug_2_dosage, + latest_blood_pressure_3.prescription_drug_3_name AS latest_blood_pressure_3_prescription_drug_3_name, + latest_blood_pressure_3.prescription_drug_3_dosage AS latest_blood_pressure_3_prescription_drug_3_dosage, + latest_blood_pressure_3.prescription_drug_4_name AS latest_blood_pressure_3_prescription_drug_4_name, + latest_blood_pressure_3.prescription_drug_4_dosage AS latest_blood_pressure_3_prescription_drug_4_dosage, + latest_blood_pressure_3.prescription_drug_5_name AS latest_blood_pressure_3_prescription_drug_5_name, + latest_blood_pressure_3.prescription_drug_5_dosage AS latest_blood_pressure_3_prescription_drug_5_dosage, + latest_blood_pressure_3.other_prescription_drugs AS latest_blood_pressure_3_other_prescription_drugs + FROM (((ranked_blood_pressures latest_blood_pressure_1 + LEFT JOIN ranked_blood_pressures latest_blood_pressure_2 ON (((latest_blood_pressure_2.patient_id = latest_blood_pressure_1.patient_id) AND (latest_blood_pressure_2.rank = 2)))) + LEFT JOIN ranked_blood_pressures latest_blood_pressure_3 ON (((latest_blood_pressure_3.patient_id = latest_blood_pressure_1.patient_id) AND (latest_blood_pressure_3.rank = 3)))) + LEFT JOIN ranked_blood_pressures latest_blood_pressure_4 ON (((latest_blood_pressure_4.patient_id = latest_blood_pressure_1.patient_id) AND (latest_blood_pressure_4.rank = 4)))) + WHERE (latest_blood_pressure_1.rank = 1) + ), ranked_blood_sugars AS ( + SELECT bs.id, + bs.patient_id, + bs.recorded_at, + bs.blood_sugar_type, + bs.blood_sugar_value, + f.name AS facility_name, + f.facility_type, + f.district, + f.state, + follow_up_facility.name AS follow_up_facility_name, + a.scheduled_date AS follow_up_date, + (GREATEST((0)::double precision, date_part('day'::text, ((a.scheduled_date)::timestamp without time zone - date_trunc('day'::text, a.device_created_at)))))::integer AS follow_up_days, + rank() OVER (PARTITION BY bs.patient_id ORDER BY bs.recorded_at DESC, bs.id) AS rank + FROM (((public.blood_sugars bs + LEFT JOIN public.facilities f ON ((bs.facility_id = f.id))) + LEFT JOIN blood_sugar_follow_up a ON ((a.bs_id = bs.id))) + LEFT JOIN public.facilities follow_up_facility ON ((follow_up_facility.id = a.facility_id))) + WHERE ((bs.deleted_at IS NULL) AND (a.deleted_at IS NULL)) + ), latest_blood_sugars AS ( + SELECT latest_blood_sugar_1.patient_id, + latest_blood_sugar_1.id AS latest_blood_sugar_1_id, + latest_blood_sugar_1.recorded_at AS latest_blood_sugar_1_recorded_at, + latest_blood_sugar_1.blood_sugar_type AS latest_blood_sugar_1_blood_sugar_type, + latest_blood_sugar_1.blood_sugar_value AS latest_blood_sugar_1_blood_sugar_value, + latest_blood_sugar_1.facility_name AS latest_blood_sugar_1_facility_name, + latest_blood_sugar_1.facility_type AS latest_blood_sugar_1_facility_type, + latest_blood_sugar_1.district AS latest_blood_sugar_1_district, + latest_blood_sugar_1.state AS latest_blood_sugar_1_state, + latest_blood_sugar_1.follow_up_facility_name AS latest_blood_sugar_1_follow_up_facility_name, + latest_blood_sugar_1.follow_up_date AS latest_blood_sugar_1_follow_up_date, + latest_blood_sugar_1.follow_up_days AS latest_blood_sugar_1_follow_up_days, + latest_blood_sugar_2.id AS latest_blood_sugar_2_id, + latest_blood_sugar_2.recorded_at AS latest_blood_sugar_2_recorded_at, + latest_blood_sugar_2.blood_sugar_type AS latest_blood_sugar_2_blood_sugar_type, + latest_blood_sugar_2.blood_sugar_value AS latest_blood_sugar_2_blood_sugar_value, + latest_blood_sugar_2.facility_name AS latest_blood_sugar_2_facility_name, + latest_blood_sugar_2.facility_type AS latest_blood_sugar_2_facility_type, + latest_blood_sugar_2.district AS latest_blood_sugar_2_district, + latest_blood_sugar_2.state AS latest_blood_sugar_2_state, + latest_blood_sugar_2.follow_up_facility_name AS latest_blood_sugar_2_follow_up_facility_name, + latest_blood_sugar_2.follow_up_date AS latest_blood_sugar_2_follow_up_date, + latest_blood_sugar_2.follow_up_days AS latest_blood_sugar_2_follow_up_days, + latest_blood_sugar_3.id AS latest_blood_sugar_3_id, + latest_blood_sugar_3.recorded_at AS latest_blood_sugar_3_recorded_at, + latest_blood_sugar_3.blood_sugar_type AS latest_blood_sugar_3_blood_sugar_type, + latest_blood_sugar_3.blood_sugar_value AS latest_blood_sugar_3_blood_sugar_value, + latest_blood_sugar_3.facility_name AS latest_blood_sugar_3_facility_name, + latest_blood_sugar_3.facility_type AS latest_blood_sugar_3_facility_type, + latest_blood_sugar_3.district AS latest_blood_sugar_3_district, + latest_blood_sugar_3.state AS latest_blood_sugar_3_state, + latest_blood_sugar_3.follow_up_facility_name AS latest_blood_sugar_3_follow_up_facility_name, + latest_blood_sugar_3.follow_up_date AS latest_blood_sugar_3_follow_up_date, + latest_blood_sugar_3.follow_up_days AS latest_blood_sugar_3_follow_up_days + FROM ((ranked_blood_sugars latest_blood_sugar_1 + LEFT JOIN ranked_blood_sugars latest_blood_sugar_2 ON (((latest_blood_sugar_2.patient_id = latest_blood_sugar_1.patient_id) AND (latest_blood_sugar_2.rank = 2)))) + LEFT JOIN ranked_blood_sugars latest_blood_sugar_3 ON (((latest_blood_sugar_3.patient_id = latest_blood_sugar_1.patient_id) AND (latest_blood_sugar_3.rank = 3)))) + WHERE (latest_blood_sugar_1.rank = 1) + ), next_scheduled_appointment AS ( + SELECT DISTINCT ON (appointments.patient_id) appointments.id, + appointments.patient_id, + appointments.facility_id, + appointments.scheduled_date, + appointments.status, + appointments.cancel_reason, + appointments.device_created_at, + appointments.device_updated_at, + appointments.created_at, + appointments.updated_at, + appointments.remind_on, + appointments.agreed_to_visit, + appointments.deleted_at, + appointments.appointment_type, + appointments.user_id, + appointments.creation_facility_id, + f.id AS appointment_facility_id, + f.name AS appointment_facility_name, + f.facility_type AS appointment_facility_type, + f.district AS appointment_district, + f.state AS appointment_state + FROM (public.appointments + LEFT JOIN public.facilities f ON (((f.id = appointments.facility_id) AND ((appointments.status)::text = 'scheduled'::text)))) + WHERE (appointments.deleted_at IS NULL) + ORDER BY appointments.patient_id, appointments.device_created_at DESC + ) + SELECT DISTINCT ON (p.id) p.id, + p.recorded_at, + p.full_name, + latest_bp_passport.id AS latest_bp_passport_id, + latest_bp_passport.identifier AS latest_bp_passport_identifier, + EXTRACT(year FROM COALESCE(age((p.date_of_birth)::timestamp with time zone), (make_interval(years => p.age) + age(p.age_updated_at)))) AS current_age, + p.gender, + p.status, + latest_phone_number.number AS latest_phone_number, + addresses.village_or_colony, + addresses.street_address, + addresses.district, + addresses.zone, + addresses.state, + assigned_facility.name AS assigned_facility_name, + assigned_facility.facility_type AS assigned_facility_type, + assigned_facility.state AS assigned_facility_state, + assigned_facility.district AS assigned_facility_district, + registration_facility.name AS registration_facility_name, + registration_facility.facility_type AS registration_facility_type, + registration_facility.state AS registration_facility_state, + registration_facility.district AS registration_facility_district, + mh.hypertension, + mh.diabetes, + GREATEST((0)::double precision, date_part('day'::text, (now() - (next_scheduled_appointment.scheduled_date)::timestamp with time zone))) AS days_overdue, + next_scheduled_appointment.id AS next_scheduled_appointment_id, + next_scheduled_appointment.scheduled_date AS next_scheduled_appointment_scheduled_date, + next_scheduled_appointment.status AS next_scheduled_appointment_status, + next_scheduled_appointment.remind_on AS next_scheduled_appointment_remind_on, + next_scheduled_appointment.appointment_facility_id AS next_scheduled_appointment_facility_id, + next_scheduled_appointment.appointment_facility_name AS next_scheduled_appointment_facility_name, + next_scheduled_appointment.appointment_facility_type AS next_scheduled_appointment_facility_type, + next_scheduled_appointment.appointment_district AS next_scheduled_appointment_district, + next_scheduled_appointment.appointment_state AS next_scheduled_appointment_state, + latest_blood_pressures.latest_blood_pressure_1_id, + latest_blood_pressures.latest_blood_pressure_1_recorded_at, + latest_blood_pressures.latest_blood_pressure_1_systolic, + latest_blood_pressures.latest_blood_pressure_1_diastolic, + latest_blood_pressures.latest_blood_pressure_1_facility_name, + latest_blood_pressures.latest_blood_pressure_1_facility_type, + latest_blood_pressures.latest_blood_pressure_1_district, + latest_blood_pressures.latest_blood_pressure_1_state, + latest_blood_pressures.latest_blood_pressure_1_follow_up_facility_name, + latest_blood_pressures.latest_blood_pressure_1_follow_up_date, + latest_blood_pressures.latest_blood_pressure_1_follow_up_days, + latest_blood_pressures.latest_blood_pressure_1_medication_updated, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_1_name, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_1_dosage, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_2_name, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_2_dosage, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_3_name, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_3_dosage, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_4_name, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_4_dosage, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_5_name, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_5_dosage, + latest_blood_pressures.latest_blood_pressure_1_other_prescription_drugs, + latest_blood_pressures.latest_blood_pressure_2_id, + latest_blood_pressures.latest_blood_pressure_2_recorded_at, + latest_blood_pressures.latest_blood_pressure_2_systolic, + latest_blood_pressures.latest_blood_pressure_2_diastolic, + latest_blood_pressures.latest_blood_pressure_2_facility_name, + latest_blood_pressures.latest_blood_pressure_2_facility_type, + latest_blood_pressures.latest_blood_pressure_2_district, + latest_blood_pressures.latest_blood_pressure_2_state, + latest_blood_pressures.latest_blood_pressure_2_follow_up_facility_name, + latest_blood_pressures.latest_blood_pressure_2_follow_up_date, + latest_blood_pressures.latest_blood_pressure_2_follow_up_days, + latest_blood_pressures.latest_blood_pressure_2_medication_updated, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_1_name, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_1_dosage, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_2_name, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_2_dosage, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_3_name, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_3_dosage, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_4_name, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_4_dosage, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_5_name, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_5_dosage, + latest_blood_pressures.latest_blood_pressure_2_other_prescription_drugs, + latest_blood_pressures.latest_blood_pressure_3_id, + latest_blood_pressures.latest_blood_pressure_3_recorded_at, + latest_blood_pressures.latest_blood_pressure_3_systolic, + latest_blood_pressures.latest_blood_pressure_3_diastolic, + latest_blood_pressures.latest_blood_pressure_3_facility_name, + latest_blood_pressures.latest_blood_pressure_3_facility_type, + latest_blood_pressures.latest_blood_pressure_3_district, + latest_blood_pressures.latest_blood_pressure_3_state, + latest_blood_pressures.latest_blood_pressure_3_follow_up_facility_name, + latest_blood_pressures.latest_blood_pressure_3_follow_up_date, + latest_blood_pressures.latest_blood_pressure_3_follow_up_days, + latest_blood_pressures.latest_blood_pressure_3_medication_updated, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_1_name, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_1_dosage, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_2_name, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_2_dosage, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_3_name, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_3_dosage, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_4_name, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_4_dosage, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_5_name, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_5_dosage, + latest_blood_pressures.latest_blood_pressure_3_other_prescription_drugs, + latest_blood_sugars.latest_blood_sugar_1_id, + latest_blood_sugars.latest_blood_sugar_1_recorded_at, + latest_blood_sugars.latest_blood_sugar_1_blood_sugar_type, + latest_blood_sugars.latest_blood_sugar_1_blood_sugar_value, + latest_blood_sugars.latest_blood_sugar_1_facility_name, + latest_blood_sugars.latest_blood_sugar_1_facility_type, + latest_blood_sugars.latest_blood_sugar_1_district, + latest_blood_sugars.latest_blood_sugar_1_state, + latest_blood_sugars.latest_blood_sugar_1_follow_up_facility_name, + latest_blood_sugars.latest_blood_sugar_1_follow_up_date, + latest_blood_sugars.latest_blood_sugar_1_follow_up_days, + latest_blood_sugars.latest_blood_sugar_2_id, + latest_blood_sugars.latest_blood_sugar_2_recorded_at, + latest_blood_sugars.latest_blood_sugar_2_blood_sugar_type, + latest_blood_sugars.latest_blood_sugar_2_blood_sugar_value, + latest_blood_sugars.latest_blood_sugar_2_facility_name, + latest_blood_sugars.latest_blood_sugar_2_facility_type, + latest_blood_sugars.latest_blood_sugar_2_district, + latest_blood_sugars.latest_blood_sugar_2_state, + latest_blood_sugars.latest_blood_sugar_2_follow_up_facility_name, + latest_blood_sugars.latest_blood_sugar_2_follow_up_date, + latest_blood_sugars.latest_blood_sugar_2_follow_up_days, + latest_blood_sugars.latest_blood_sugar_3_id, + latest_blood_sugars.latest_blood_sugar_3_recorded_at, + latest_blood_sugars.latest_blood_sugar_3_blood_sugar_type, + latest_blood_sugars.latest_blood_sugar_3_blood_sugar_value, + latest_blood_sugars.latest_blood_sugar_3_facility_name, + latest_blood_sugars.latest_blood_sugar_3_facility_type, + latest_blood_sugars.latest_blood_sugar_3_district, + latest_blood_sugars.latest_blood_sugar_3_state, + latest_blood_sugars.latest_blood_sugar_3_follow_up_facility_name, + latest_blood_sugars.latest_blood_sugar_3_follow_up_date, + latest_blood_sugars.latest_blood_sugar_3_follow_up_days + FROM (((((((((public.patients p + LEFT JOIN latest_bp_passport ON ((latest_bp_passport.patient_id = p.id))) + LEFT JOIN latest_phone_number ON ((latest_phone_number.patient_id = p.id))) + LEFT JOIN public.addresses ON ((addresses.id = p.address_id))) + LEFT JOIN public.facilities assigned_facility ON ((assigned_facility.id = p.assigned_facility_id))) + LEFT JOIN public.facilities registration_facility ON ((registration_facility.id = p.registration_facility_id))) + LEFT JOIN latest_medical_history mh ON ((mh.patient_id = p.id))) + LEFT JOIN latest_blood_pressures ON ((latest_blood_pressures.patient_id = p.id))) + LEFT JOIN latest_blood_sugars ON ((latest_blood_sugars.patient_id = p.id))) + LEFT JOIN next_scheduled_appointment ON ((next_scheduled_appointment.patient_id = p.id))) + WHERE (p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) + WITH NO DATA; + SQL + + add_index :materialized_patient_summaries, [:id], unique: true, name: "index_materialized_patient_summaries_on_id" + end + + def down + drop_view :materialized_patient_summaries, materialized: true + execute <<~SQL + CREATE MATERIALIZED VIEW public.materialized_patient_summaries AS + WITH latest_bp_passport AS ( + SELECT DISTINCT ON (patient_business_identifiers.patient_id) patient_business_identifiers.id, + patient_business_identifiers.identifier, + patient_business_identifiers.identifier_type, + patient_business_identifiers.patient_id, + patient_business_identifiers.metadata_version, + patient_business_identifiers.metadata, + patient_business_identifiers.device_created_at, + patient_business_identifiers.device_updated_at, + patient_business_identifiers.deleted_at, + patient_business_identifiers.created_at, + patient_business_identifiers.updated_at + FROM public.patient_business_identifiers + WHERE (((patient_business_identifiers.identifier_type)::text = 'simple_bp_passport'::text) AND (patient_business_identifiers.deleted_at IS NULL)) + ORDER BY patient_business_identifiers.patient_id, patient_business_identifiers.device_created_at DESC + ), latest_phone_number AS ( + SELECT DISTINCT ON (patient_phone_numbers.patient_id) patient_phone_numbers.id, + patient_phone_numbers.number, + patient_phone_numbers.phone_type, + patient_phone_numbers.active, + patient_phone_numbers.created_at, + patient_phone_numbers.updated_at, + patient_phone_numbers.patient_id, + patient_phone_numbers.device_created_at, + patient_phone_numbers.device_updated_at, + patient_phone_numbers.deleted_at, + patient_phone_numbers.dnd_status + FROM public.patient_phone_numbers + WHERE (patient_phone_numbers.deleted_at IS NULL) + ORDER BY patient_phone_numbers.patient_id, patient_phone_numbers.device_created_at DESC + ), latest_medical_history AS ( + SELECT DISTINCT ON (medical_histories.patient_id) medical_histories.id, + medical_histories.patient_id, + medical_histories.prior_heart_attack_boolean, + medical_histories.prior_stroke_boolean, + medical_histories.chronic_kidney_disease_boolean, + medical_histories.receiving_treatment_for_hypertension_boolean, + medical_histories.diabetes_boolean, + medical_histories.device_created_at, + medical_histories.device_updated_at, + medical_histories.created_at, + medical_histories.updated_at, + medical_histories.diagnosed_with_hypertension_boolean, + medical_histories.prior_heart_attack, + medical_histories.prior_stroke, + medical_histories.chronic_kidney_disease, + medical_histories.receiving_treatment_for_hypertension, + medical_histories.diabetes, + medical_histories.diagnosed_with_hypertension, + medical_histories.deleted_at, + medical_histories.user_id, + medical_histories.hypertension, + medical_histories.receiving_treatment_for_diabetes, + medical_histories.smoking, + medical_histories.cholesterol + FROM public.medical_histories + WHERE (medical_histories.deleted_at IS NULL) + ), ranked_prescription_drugs AS ( + SELECT bp.id AS bp_id, + array_agg(ARRAY[prescription_drugs.name, prescription_drugs.dosage] ORDER BY prescription_drugs.is_protocol_drug DESC, prescription_drugs.name, prescription_drugs.device_created_at DESC) AS blood_pressure_drugs, + array_agg((((prescription_drugs.name)::text || '-'::text) || (prescription_drugs.dosage)::text) ORDER BY prescription_drugs.is_protocol_drug DESC, prescription_drugs.name, prescription_drugs.device_created_at DESC) AS drug_strings + FROM (public.blood_pressures bp + JOIN public.prescription_drugs ON (((prescription_drugs.patient_id = bp.patient_id) AND (date(prescription_drugs.device_created_at) <= date(bp.recorded_at)) AND ((prescription_drugs.is_deleted IS FALSE) OR ((prescription_drugs.is_deleted IS TRUE) AND (date(prescription_drugs.device_updated_at) > date(bp.recorded_at))))))) + WHERE ((bp.deleted_at IS NULL) AND (prescription_drugs.deleted_at IS NULL)) + GROUP BY bp.id + ), blood_pressure_follow_up AS ( + SELECT DISTINCT ON (bp.patient_id, (date(bp.recorded_at))) bp.id AS bp_id, + appointments.id, + appointments.patient_id, + appointments.facility_id, + appointments.scheduled_date, + appointments.status, + appointments.cancel_reason, + appointments.device_created_at, + appointments.device_updated_at, + appointments.created_at, + appointments.updated_at, + appointments.remind_on, + appointments.agreed_to_visit, + appointments.deleted_at, + appointments.appointment_type, + appointments.user_id, + appointments.creation_facility_id + FROM (public.blood_pressures bp + JOIN public.appointments ON (((appointments.patient_id = bp.patient_id) AND (date(appointments.device_created_at) = date(bp.recorded_at))))) + ORDER BY bp.patient_id, (date(bp.recorded_at)), appointments.device_created_at DESC + ), blood_sugar_follow_up AS ( + SELECT DISTINCT ON (bs.patient_id, (date(bs.recorded_at))) bs.id AS bs_id, + appointments.id, + appointments.patient_id, + appointments.facility_id, + appointments.scheduled_date, + appointments.status, + appointments.cancel_reason, + appointments.device_created_at, + appointments.device_updated_at, + appointments.created_at, + appointments.updated_at, + appointments.remind_on, + appointments.agreed_to_visit, + appointments.deleted_at, + appointments.appointment_type, + appointments.user_id, + appointments.creation_facility_id + FROM (public.blood_sugars bs + JOIN public.appointments ON (((appointments.patient_id = bs.patient_id) AND (date(appointments.device_created_at) = date(bs.recorded_at))))) + ORDER BY bs.patient_id, (date(bs.recorded_at)), appointments.device_created_at DESC + ), ranked_blood_pressures AS ( + SELECT bp.id, + bp.patient_id, + bp.recorded_at, + bp.systolic, + bp.diastolic, + f.name AS facility_name, + f.facility_type, + f.district, + f.state, + follow_up_facility.name AS follow_up_facility_name, + a.scheduled_date AS follow_up_date, + (GREATEST((0)::double precision, date_part('day'::text, ((a.scheduled_date)::timestamp without time zone - date_trunc('day'::text, a.device_created_at)))))::integer AS follow_up_days, + bp_drugs.blood_pressure_drugs[1][1] AS prescription_drug_1_name, + bp_drugs.blood_pressure_drugs[1][2] AS prescription_drug_1_dosage, + bp_drugs.blood_pressure_drugs[2][1] AS prescription_drug_2_name, + bp_drugs.blood_pressure_drugs[2][2] AS prescription_drug_2_dosage, + bp_drugs.blood_pressure_drugs[3][1] AS prescription_drug_3_name, + bp_drugs.blood_pressure_drugs[3][2] AS prescription_drug_3_dosage, + bp_drugs.blood_pressure_drugs[4][1] AS prescription_drug_4_name, + bp_drugs.blood_pressure_drugs[4][2] AS prescription_drug_4_dosage, + bp_drugs.blood_pressure_drugs[5][1] AS prescription_drug_5_name, + bp_drugs.blood_pressure_drugs[5][2] AS prescription_drug_5_dosage, + ( SELECT string_agg(value.value, ', '::text) AS string_agg + FROM unnest(bp_drugs.drug_strings[6:]) value(value)) AS other_prescription_drugs, + ( SELECT string_agg(value.value, ', '::text) AS string_agg + FROM unnest(bp_drugs.drug_strings) value(value)) AS all_prescription_drugs, + rank() OVER (PARTITION BY bp.patient_id ORDER BY bp.recorded_at DESC, bp.id) AS rank + FROM ((((public.blood_pressures bp + LEFT JOIN public.facilities f ON ((bp.facility_id = f.id))) + LEFT JOIN ranked_prescription_drugs bp_drugs ON ((bp.id = bp_drugs.bp_id))) + LEFT JOIN blood_pressure_follow_up a ON ((a.bp_id = bp.id))) + LEFT JOIN public.facilities follow_up_facility ON ((follow_up_facility.id = a.facility_id))) + WHERE ((bp.deleted_at IS NULL) AND (a.deleted_at IS NULL)) + ), latest_blood_pressures AS ( + SELECT latest_blood_pressure_1.patient_id, + latest_blood_pressure_1.id AS latest_blood_pressure_1_id, + latest_blood_pressure_1.recorded_at AS latest_blood_pressure_1_recorded_at, + latest_blood_pressure_1.systolic AS latest_blood_pressure_1_systolic, + latest_blood_pressure_1.diastolic AS latest_blood_pressure_1_diastolic, + latest_blood_pressure_1.facility_name AS latest_blood_pressure_1_facility_name, + latest_blood_pressure_1.facility_type AS latest_blood_pressure_1_facility_type, + latest_blood_pressure_1.district AS latest_blood_pressure_1_district, + latest_blood_pressure_1.state AS latest_blood_pressure_1_state, + latest_blood_pressure_1.follow_up_facility_name AS latest_blood_pressure_1_follow_up_facility_name, + latest_blood_pressure_1.follow_up_date AS latest_blood_pressure_1_follow_up_date, + latest_blood_pressure_1.follow_up_days AS latest_blood_pressure_1_follow_up_days, + (latest_blood_pressure_1.all_prescription_drugs <> latest_blood_pressure_2.all_prescription_drugs) AS latest_blood_pressure_1_medication_updated, + latest_blood_pressure_1.prescription_drug_1_name AS latest_blood_pressure_1_prescription_drug_1_name, + latest_blood_pressure_1.prescription_drug_1_dosage AS latest_blood_pressure_1_prescription_drug_1_dosage, + latest_blood_pressure_1.prescription_drug_2_name AS latest_blood_pressure_1_prescription_drug_2_name, + latest_blood_pressure_1.prescription_drug_2_dosage AS latest_blood_pressure_1_prescription_drug_2_dosage, + latest_blood_pressure_1.prescription_drug_3_name AS latest_blood_pressure_1_prescription_drug_3_name, + latest_blood_pressure_1.prescription_drug_3_dosage AS latest_blood_pressure_1_prescription_drug_3_dosage, + latest_blood_pressure_1.prescription_drug_4_name AS latest_blood_pressure_1_prescription_drug_4_name, + latest_blood_pressure_1.prescription_drug_4_dosage AS latest_blood_pressure_1_prescription_drug_4_dosage, + latest_blood_pressure_1.prescription_drug_5_name AS latest_blood_pressure_1_prescription_drug_5_name, + latest_blood_pressure_1.prescription_drug_5_dosage AS latest_blood_pressure_1_prescription_drug_5_dosage, + latest_blood_pressure_1.other_prescription_drugs AS latest_blood_pressure_1_other_prescription_drugs, + latest_blood_pressure_2.id AS latest_blood_pressure_2_id, + latest_blood_pressure_2.recorded_at AS latest_blood_pressure_2_recorded_at, + latest_blood_pressure_2.systolic AS latest_blood_pressure_2_systolic, + latest_blood_pressure_2.diastolic AS latest_blood_pressure_2_diastolic, + latest_blood_pressure_2.facility_name AS latest_blood_pressure_2_facility_name, + latest_blood_pressure_2.facility_type AS latest_blood_pressure_2_facility_type, + latest_blood_pressure_2.district AS latest_blood_pressure_2_district, + latest_blood_pressure_2.state AS latest_blood_pressure_2_state, + latest_blood_pressure_2.follow_up_facility_name AS latest_blood_pressure_2_follow_up_facility_name, + latest_blood_pressure_2.follow_up_date AS latest_blood_pressure_2_follow_up_date, + latest_blood_pressure_2.follow_up_days AS latest_blood_pressure_2_follow_up_days, + (latest_blood_pressure_2.all_prescription_drugs <> latest_blood_pressure_3.all_prescription_drugs) AS latest_blood_pressure_2_medication_updated, + latest_blood_pressure_2.prescription_drug_1_name AS latest_blood_pressure_2_prescription_drug_1_name, + latest_blood_pressure_2.prescription_drug_1_dosage AS latest_blood_pressure_2_prescription_drug_1_dosage, + latest_blood_pressure_2.prescription_drug_2_name AS latest_blood_pressure_2_prescription_drug_2_name, + latest_blood_pressure_2.prescription_drug_2_dosage AS latest_blood_pressure_2_prescription_drug_2_dosage, + latest_blood_pressure_2.prescription_drug_3_name AS latest_blood_pressure_2_prescription_drug_3_name, + latest_blood_pressure_2.prescription_drug_3_dosage AS latest_blood_pressure_2_prescription_drug_3_dosage, + latest_blood_pressure_2.prescription_drug_4_name AS latest_blood_pressure_2_prescription_drug_4_name, + latest_blood_pressure_2.prescription_drug_4_dosage AS latest_blood_pressure_2_prescription_drug_4_dosage, + latest_blood_pressure_2.prescription_drug_5_name AS latest_blood_pressure_2_prescription_drug_5_name, + latest_blood_pressure_2.prescription_drug_5_dosage AS latest_blood_pressure_2_prescription_drug_5_dosage, + latest_blood_pressure_2.other_prescription_drugs AS latest_blood_pressure_2_other_prescription_drugs, + latest_blood_pressure_3.id AS latest_blood_pressure_3_id, + latest_blood_pressure_3.recorded_at AS latest_blood_pressure_3_recorded_at, + latest_blood_pressure_3.systolic AS latest_blood_pressure_3_systolic, + latest_blood_pressure_3.diastolic AS latest_blood_pressure_3_diastolic, + latest_blood_pressure_3.facility_name AS latest_blood_pressure_3_facility_name, + latest_blood_pressure_3.facility_type AS latest_blood_pressure_3_facility_type, + latest_blood_pressure_3.district AS latest_blood_pressure_3_district, + latest_blood_pressure_3.state AS latest_blood_pressure_3_state, + latest_blood_pressure_3.follow_up_facility_name AS latest_blood_pressure_3_follow_up_facility_name, + latest_blood_pressure_3.follow_up_date AS latest_blood_pressure_3_follow_up_date, + latest_blood_pressure_3.follow_up_days AS latest_blood_pressure_3_follow_up_days, + (latest_blood_pressure_3.all_prescription_drugs <> latest_blood_pressure_4.all_prescription_drugs) AS latest_blood_pressure_3_medication_updated, + latest_blood_pressure_3.prescription_drug_1_name AS latest_blood_pressure_3_prescription_drug_1_name, + latest_blood_pressure_3.prescription_drug_1_dosage AS latest_blood_pressure_3_prescription_drug_1_dosage, + latest_blood_pressure_3.prescription_drug_2_name AS latest_blood_pressure_3_prescription_drug_2_name, + latest_blood_pressure_3.prescription_drug_2_dosage AS latest_blood_pressure_3_prescription_drug_2_dosage, + latest_blood_pressure_3.prescription_drug_3_name AS latest_blood_pressure_3_prescription_drug_3_name, + latest_blood_pressure_3.prescription_drug_3_dosage AS latest_blood_pressure_3_prescription_drug_3_dosage, + latest_blood_pressure_3.prescription_drug_4_name AS latest_blood_pressure_3_prescription_drug_4_name, + latest_blood_pressure_3.prescription_drug_4_dosage AS latest_blood_pressure_3_prescription_drug_4_dosage, + latest_blood_pressure_3.prescription_drug_5_name AS latest_blood_pressure_3_prescription_drug_5_name, + latest_blood_pressure_3.prescription_drug_5_dosage AS latest_blood_pressure_3_prescription_drug_5_dosage, + latest_blood_pressure_3.other_prescription_drugs AS latest_blood_pressure_3_other_prescription_drugs + FROM (((ranked_blood_pressures latest_blood_pressure_1 + LEFT JOIN ranked_blood_pressures latest_blood_pressure_2 ON (((latest_blood_pressure_2.patient_id = latest_blood_pressure_1.patient_id) AND (latest_blood_pressure_2.rank = 2)))) + LEFT JOIN ranked_blood_pressures latest_blood_pressure_3 ON (((latest_blood_pressure_3.patient_id = latest_blood_pressure_1.patient_id) AND (latest_blood_pressure_3.rank = 3)))) + LEFT JOIN ranked_blood_pressures latest_blood_pressure_4 ON (((latest_blood_pressure_4.patient_id = latest_blood_pressure_1.patient_id) AND (latest_blood_pressure_4.rank = 4)))) + WHERE (latest_blood_pressure_1.rank = 1) + ), ranked_blood_sugars AS ( + SELECT bs.id, + bs.patient_id, + bs.recorded_at, + bs.blood_sugar_type, + bs.blood_sugar_value, + f.name AS facility_name, + f.facility_type, + f.district, + f.state, + follow_up_facility.name AS follow_up_facility_name, + a.scheduled_date AS follow_up_date, + (GREATEST((0)::double precision, date_part('day'::text, ((a.scheduled_date)::timestamp without time zone - date_trunc('day'::text, a.device_created_at)))))::integer AS follow_up_days, + rank() OVER (PARTITION BY bs.patient_id ORDER BY bs.recorded_at DESC, bs.id) AS rank + FROM (((public.blood_sugars bs + LEFT JOIN public.facilities f ON ((bs.facility_id = f.id))) + LEFT JOIN blood_sugar_follow_up a ON ((a.bs_id = bs.id))) + LEFT JOIN public.facilities follow_up_facility ON ((follow_up_facility.id = a.facility_id))) + WHERE ((bs.deleted_at IS NULL) AND (a.deleted_at IS NULL)) + ), latest_blood_sugars AS ( + SELECT latest_blood_sugar_1.patient_id, + latest_blood_sugar_1.id AS latest_blood_sugar_1_id, + latest_blood_sugar_1.recorded_at AS latest_blood_sugar_1_recorded_at, + latest_blood_sugar_1.blood_sugar_type AS latest_blood_sugar_1_blood_sugar_type, + latest_blood_sugar_1.blood_sugar_value AS latest_blood_sugar_1_blood_sugar_value, + latest_blood_sugar_1.facility_name AS latest_blood_sugar_1_facility_name, + latest_blood_sugar_1.facility_type AS latest_blood_sugar_1_facility_type, + latest_blood_sugar_1.district AS latest_blood_sugar_1_district, + latest_blood_sugar_1.state AS latest_blood_sugar_1_state, + latest_blood_sugar_1.follow_up_facility_name AS latest_blood_sugar_1_follow_up_facility_name, + latest_blood_sugar_1.follow_up_date AS latest_blood_sugar_1_follow_up_date, + latest_blood_sugar_1.follow_up_days AS latest_blood_sugar_1_follow_up_days, + latest_blood_sugar_2.id AS latest_blood_sugar_2_id, + latest_blood_sugar_2.recorded_at AS latest_blood_sugar_2_recorded_at, + latest_blood_sugar_2.blood_sugar_type AS latest_blood_sugar_2_blood_sugar_type, + latest_blood_sugar_2.blood_sugar_value AS latest_blood_sugar_2_blood_sugar_value, + latest_blood_sugar_2.facility_name AS latest_blood_sugar_2_facility_name, + latest_blood_sugar_2.facility_type AS latest_blood_sugar_2_facility_type, + latest_blood_sugar_2.district AS latest_blood_sugar_2_district, + latest_blood_sugar_2.state AS latest_blood_sugar_2_state, + latest_blood_sugar_2.follow_up_facility_name AS latest_blood_sugar_2_follow_up_facility_name, + latest_blood_sugar_2.follow_up_date AS latest_blood_sugar_2_follow_up_date, + latest_blood_sugar_2.follow_up_days AS latest_blood_sugar_2_follow_up_days, + latest_blood_sugar_3.id AS latest_blood_sugar_3_id, + latest_blood_sugar_3.recorded_at AS latest_blood_sugar_3_recorded_at, + latest_blood_sugar_3.blood_sugar_type AS latest_blood_sugar_3_blood_sugar_type, + latest_blood_sugar_3.blood_sugar_value AS latest_blood_sugar_3_blood_sugar_value, + latest_blood_sugar_3.facility_name AS latest_blood_sugar_3_facility_name, + latest_blood_sugar_3.facility_type AS latest_blood_sugar_3_facility_type, + latest_blood_sugar_3.district AS latest_blood_sugar_3_district, + latest_blood_sugar_3.state AS latest_blood_sugar_3_state, + latest_blood_sugar_3.follow_up_facility_name AS latest_blood_sugar_3_follow_up_facility_name, + latest_blood_sugar_3.follow_up_date AS latest_blood_sugar_3_follow_up_date, + latest_blood_sugar_3.follow_up_days AS latest_blood_sugar_3_follow_up_days + FROM ((ranked_blood_sugars latest_blood_sugar_1 + LEFT JOIN ranked_blood_sugars latest_blood_sugar_2 ON (((latest_blood_sugar_2.patient_id = latest_blood_sugar_1.patient_id) AND (latest_blood_sugar_2.rank = 2)))) + LEFT JOIN ranked_blood_sugars latest_blood_sugar_3 ON (((latest_blood_sugar_3.patient_id = latest_blood_sugar_1.patient_id) AND (latest_blood_sugar_3.rank = 3)))) + WHERE (latest_blood_sugar_1.rank = 1) + ), next_scheduled_appointment AS ( + SELECT DISTINCT ON (appointments.patient_id) appointments.id, + appointments.patient_id, + appointments.facility_id, + appointments.scheduled_date, + appointments.status, + appointments.cancel_reason, + appointments.device_created_at, + appointments.device_updated_at, + appointments.created_at, + appointments.updated_at, + appointments.remind_on, + appointments.agreed_to_visit, + appointments.deleted_at, + appointments.appointment_type, + appointments.user_id, + appointments.creation_facility_id, + f.id AS appointment_facility_id, + f.name AS appointment_facility_name, + f.facility_type AS appointment_facility_type, + f.district AS appointment_district, + f.state AS appointment_state + FROM (public.appointments + LEFT JOIN public.facilities f ON (((f.id = appointments.facility_id) AND ((appointments.status)::text = 'scheduled'::text)))) + WHERE (appointments.deleted_at IS NULL) + ORDER BY appointments.patient_id, appointments.device_created_at DESC + ) + SELECT DISTINCT ON (p.id) p.id, + p.recorded_at, + p.full_name, + latest_bp_passport.id AS latest_bp_passport_id, + latest_bp_passport.identifier AS latest_bp_passport_identifier, + EXTRACT(year FROM COALESCE(age((p.date_of_birth)::timestamp with time zone), (make_interval(years => p.age) + age(p.age_updated_at)))) AS current_age, + p.gender, + p.status, + latest_phone_number.number AS latest_phone_number, + addresses.village_or_colony, + addresses.street_address, + addresses.district, + addresses.zone, + addresses.state, + assigned_facility.name AS assigned_facility_name, + assigned_facility.facility_type AS assigned_facility_type, + assigned_facility.state AS assigned_facility_state, + assigned_facility.district AS assigned_facility_district, + registration_facility.name AS registration_facility_name, + registration_facility.facility_type AS registration_facility_type, + registration_facility.state AS registration_facility_state, + registration_facility.district AS registration_facility_district, + mh.hypertension, + mh.diabetes, + GREATEST((0)::double precision, date_part('day'::text, (now() - (next_scheduled_appointment.scheduled_date)::timestamp with time zone))) AS days_overdue, + next_scheduled_appointment.id AS next_scheduled_appointment_id, + next_scheduled_appointment.scheduled_date AS next_scheduled_appointment_scheduled_date, + next_scheduled_appointment.status AS next_scheduled_appointment_status, + next_scheduled_appointment.remind_on AS next_scheduled_appointment_remind_on, + next_scheduled_appointment.appointment_facility_id AS next_scheduled_appointment_facility_id, + next_scheduled_appointment.appointment_facility_name AS next_scheduled_appointment_facility_name, + next_scheduled_appointment.appointment_facility_type AS next_scheduled_appointment_facility_type, + next_scheduled_appointment.appointment_district AS next_scheduled_appointment_district, + next_scheduled_appointment.appointment_state AS next_scheduled_appointment_state, + latest_blood_pressures.latest_blood_pressure_1_id, + latest_blood_pressures.latest_blood_pressure_1_recorded_at, + latest_blood_pressures.latest_blood_pressure_1_systolic, + latest_blood_pressures.latest_blood_pressure_1_diastolic, + latest_blood_pressures.latest_blood_pressure_1_facility_name, + latest_blood_pressures.latest_blood_pressure_1_facility_type, + latest_blood_pressures.latest_blood_pressure_1_district, + latest_blood_pressures.latest_blood_pressure_1_state, + latest_blood_pressures.latest_blood_pressure_1_follow_up_facility_name, + latest_blood_pressures.latest_blood_pressure_1_follow_up_date, + latest_blood_pressures.latest_blood_pressure_1_follow_up_days, + latest_blood_pressures.latest_blood_pressure_1_medication_updated, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_1_name, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_1_dosage, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_2_name, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_2_dosage, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_3_name, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_3_dosage, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_4_name, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_4_dosage, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_5_name, + latest_blood_pressures.latest_blood_pressure_1_prescription_drug_5_dosage, + latest_blood_pressures.latest_blood_pressure_1_other_prescription_drugs, + latest_blood_pressures.latest_blood_pressure_2_id, + latest_blood_pressures.latest_blood_pressure_2_recorded_at, + latest_blood_pressures.latest_blood_pressure_2_systolic, + latest_blood_pressures.latest_blood_pressure_2_diastolic, + latest_blood_pressures.latest_blood_pressure_2_facility_name, + latest_blood_pressures.latest_blood_pressure_2_facility_type, + latest_blood_pressures.latest_blood_pressure_2_district, + latest_blood_pressures.latest_blood_pressure_2_state, + latest_blood_pressures.latest_blood_pressure_2_follow_up_facility_name, + latest_blood_pressures.latest_blood_pressure_2_follow_up_date, + latest_blood_pressures.latest_blood_pressure_2_follow_up_days, + latest_blood_pressures.latest_blood_pressure_2_medication_updated, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_1_name, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_1_dosage, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_2_name, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_2_dosage, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_3_name, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_3_dosage, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_4_name, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_4_dosage, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_5_name, + latest_blood_pressures.latest_blood_pressure_2_prescription_drug_5_dosage, + latest_blood_pressures.latest_blood_pressure_2_other_prescription_drugs, + latest_blood_pressures.latest_blood_pressure_3_id, + latest_blood_pressures.latest_blood_pressure_3_recorded_at, + latest_blood_pressures.latest_blood_pressure_3_systolic, + latest_blood_pressures.latest_blood_pressure_3_diastolic, + latest_blood_pressures.latest_blood_pressure_3_facility_name, + latest_blood_pressures.latest_blood_pressure_3_facility_type, + latest_blood_pressures.latest_blood_pressure_3_district, + latest_blood_pressures.latest_blood_pressure_3_state, + latest_blood_pressures.latest_blood_pressure_3_follow_up_facility_name, + latest_blood_pressures.latest_blood_pressure_3_follow_up_date, + latest_blood_pressures.latest_blood_pressure_3_follow_up_days, + latest_blood_pressures.latest_blood_pressure_3_medication_updated, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_1_name, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_1_dosage, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_2_name, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_2_dosage, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_3_name, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_3_dosage, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_4_name, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_4_dosage, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_5_name, + latest_blood_pressures.latest_blood_pressure_3_prescription_drug_5_dosage, + latest_blood_pressures.latest_blood_pressure_3_other_prescription_drugs, + latest_blood_sugars.latest_blood_sugar_1_id, + latest_blood_sugars.latest_blood_sugar_1_recorded_at, + latest_blood_sugars.latest_blood_sugar_1_blood_sugar_type, + latest_blood_sugars.latest_blood_sugar_1_blood_sugar_value, + latest_blood_sugars.latest_blood_sugar_1_facility_name, + latest_blood_sugars.latest_blood_sugar_1_facility_type, + latest_blood_sugars.latest_blood_sugar_1_district, + latest_blood_sugars.latest_blood_sugar_1_state, + latest_blood_sugars.latest_blood_sugar_1_follow_up_facility_name, + latest_blood_sugars.latest_blood_sugar_1_follow_up_date, + latest_blood_sugars.latest_blood_sugar_1_follow_up_days, + latest_blood_sugars.latest_blood_sugar_2_id, + latest_blood_sugars.latest_blood_sugar_2_recorded_at, + latest_blood_sugars.latest_blood_sugar_2_blood_sugar_type, + latest_blood_sugars.latest_blood_sugar_2_blood_sugar_value, + latest_blood_sugars.latest_blood_sugar_2_facility_name, + latest_blood_sugars.latest_blood_sugar_2_facility_type, + latest_blood_sugars.latest_blood_sugar_2_district, + latest_blood_sugars.latest_blood_sugar_2_state, + latest_blood_sugars.latest_blood_sugar_2_follow_up_facility_name, + latest_blood_sugars.latest_blood_sugar_2_follow_up_date, + latest_blood_sugars.latest_blood_sugar_2_follow_up_days, + latest_blood_sugars.latest_blood_sugar_3_id, + latest_blood_sugars.latest_blood_sugar_3_recorded_at, + latest_blood_sugars.latest_blood_sugar_3_blood_sugar_type, + latest_blood_sugars.latest_blood_sugar_3_blood_sugar_value, + latest_blood_sugars.latest_blood_sugar_3_facility_name, + latest_blood_sugars.latest_blood_sugar_3_facility_type, + latest_blood_sugars.latest_blood_sugar_3_district, + latest_blood_sugars.latest_blood_sugar_3_state, + latest_blood_sugars.latest_blood_sugar_3_follow_up_facility_name, + latest_blood_sugars.latest_blood_sugar_3_follow_up_date, + latest_blood_sugars.latest_blood_sugar_3_follow_up_days + FROM (((((((((public.patients p + LEFT JOIN latest_bp_passport ON ((latest_bp_passport.patient_id = p.id))) + LEFT JOIN latest_phone_number ON ((latest_phone_number.patient_id = p.id))) + LEFT JOIN public.addresses ON ((addresses.id = p.address_id))) + LEFT JOIN public.facilities assigned_facility ON ((assigned_facility.id = p.assigned_facility_id))) + LEFT JOIN public.facilities registration_facility ON ((registration_facility.id = p.registration_facility_id))) + LEFT JOIN latest_medical_history mh ON ((mh.patient_id = p.id))) + LEFT JOIN latest_blood_pressures ON ((latest_blood_pressures.patient_id = p.id))) + LEFT JOIN latest_blood_sugars ON ((latest_blood_sugars.patient_id = p.id))) + LEFT JOIN next_scheduled_appointment ON ((next_scheduled_appointment.patient_id = p.id))) + WHERE (p.deleted_at IS NULL) + WITH NO DATA; + SQL + + add_index :materialized_patient_summaries, [:id], unique: true, name: "index_materialized_patient_summaries_on_id" + end +end diff --git a/db/migrate/20251215113615_add_diagnosed_confirmed_at_index_to_patients.rb b/db/migrate/20251215113615_add_diagnosed_confirmed_at_index_to_patients.rb new file mode 100644 index 0000000000..bba7f181a4 --- /dev/null +++ b/db/migrate/20251215113615_add_diagnosed_confirmed_at_index_to_patients.rb @@ -0,0 +1,11 @@ +class AddDiagnosedConfirmedAtIndexToPatients < ActiveRecord::Migration[6.1] + self.disable_ddl_transaction = true + + def up + add_index :patients, [:diagnosed_confirmed_at], name: "index_patients_on_diagnosed_confirmed_at", algorithm: :concurrently + end + + def down + remove_index :patients, name: "index_patients_on_diagnosed_confirmed_at" + end +end diff --git a/db/structure.sql b/db/structure.sql index 0ff07a878e..9476b36e23 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -236,7 +236,8 @@ CREATE TABLE simple_reporting.reporting_patient_states ( diabetes_treatment_outcome_in_last_3_months text, diabetes_treatment_outcome_in_last_2_months text, diabetes_treatment_outcome_in_quarter text, - titrated boolean + titrated boolean, + diagnosed_confirmed_at timestamp without time zone ) PARTITION BY LIST (month_date); @@ -333,12 +334,12 @@ CREATE OR REPLACE FUNCTION simple_reporting.reporting_patient_states_table_funct visits.visited_facility_ids, -- Relative time calculations - (cal.year - DATE_PART('year', p.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) * 12 + - (cal.month - DATE_PART('month', p.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) + (cal.year - DATE_PART('year', p.diagnosed_confirmed_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) * 12 + + (cal.month - DATE_PART('month', p.diagnosed_confirmed_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) AS months_since_registration, - (cal.year - DATE_PART('year', p.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) * 4 + - (cal.quarter - DATE_PART('quarter', p.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) + (cal.year - DATE_PART('year', p.diagnosed_confirmed_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) * 4 + + (cal.quarter - DATE_PART('quarter', p.diagnosed_confirmed_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) AS quarters_since_registration, visits.months_since_visit, @@ -358,8 +359,8 @@ CREATE OR REPLACE FUNCTION simple_reporting.reporting_patient_states_table_funct CASE WHEN p.status = 'dead' THEN 'dead' WHEN ( - (cal.year - DATE_PART('year', p.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) * 12 + - (cal.month - DATE_PART('month', p.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) < 12 + (cal.year - DATE_PART('year', p.diagnosed_confirmed_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) * 12 + + (cal.month - DATE_PART('month', p.diagnosed_confirmed_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))) < 12 OR visits.months_since_visit < 12 ) THEN 'under_care' ELSE 'lost_to_follow_up' @@ -412,14 +413,15 @@ CREATE OR REPLACE FUNCTION simple_reporting.reporting_patient_states_table_funct current_meds.enalapril > past_meds.enalapril OR current_meds.chlorthalidone > past_meds.chlorthalidone OR current_meds.hydrochlorothiazide > past_meds.hydrochlorothiazide - ) AS titrated + ) AS titrated, + p.diagnosed_confirmed_at AT TIME ZONE 'UTC' AT TIME ZONE 'UTC' AS diagnosed_confirmed_at FROM public.patients p JOIN public.reporting_months cal ON cal.month_date = $1 - AND p.recorded_at <= cal.month_date + INTERVAL '1 month' + INTERVAL '1 day' + AND p.diagnosed_confirmed_at <= cal.month_date + INTERVAL '1 month' + INTERVAL '1 day' AND (( - to_char(timezone((SELECT current_setting('TIMEZONE'::text) AS current_setting), TIMEZONE('UTC'::text, p.recorded_at)), 'YYYY-MM'::text) <= + to_char(timezone((SELECT current_setting('TIMEZONE'::text) AS current_setting), TIMEZONE('UTC'::text, p.diagnosed_confirmed_at)), 'YYYY-MM'::text) <= to_char((cal.month_date)::timestamp with time zone, 'YYYY-MM'::text)) ) @@ -432,8 +434,12 @@ CREATE OR REPLACE FUNCTION simple_reporting.reporting_patient_states_table_funct LEFT OUTER JOIN public.reporting_patient_visits visits ON p.id = visits.patient_id AND cal.month_date = visits.month_date - LEFT OUTER JOIN public.medical_histories mh - ON p.id = mh.patient_id AND mh.deleted_at IS NULL + LEFT OUTER JOIN LATERAL ( + SELECT DISTINCT ON (patient_id) * + FROM public.medical_histories + WHERE patient_id = p.id AND deleted_at IS NULL + ORDER BY patient_id, device_updated_at DESC + ) mh ON true LEFT OUTER JOIN public.reporting_prescriptions current_meds ON current_meds.patient_id = p.id AND cal.month_date = current_meds.month_date @@ -447,7 +453,9 @@ CREATE OR REPLACE FUNCTION simple_reporting.reporting_patient_states_table_funct INNER JOIN public.reporting_facilities assigned_facility ON assigned_facility.facility_id = p.assigned_facility_id - WHERE p.deleted_at IS NULL; + WHERE p.deleted_at IS NULL + AND p.diagnosed_confirmed_at IS NOT NULL + ORDER BY p.id; END; $_$; @@ -642,40 +650,11 @@ CREATE TABLE public.medical_histories ( receiving_treatment_for_diabetes text, smoking text, cholesterol integer, - smokeless_tobacco character varying + smokeless_tobacco character varying, + htn_diagnosed_at timestamp without time zone, + dm_diagnosed_at timestamp without time zone ); - --- --- Name: blood_pressures_per_facility_per_days; Type: MATERIALIZED VIEW; Schema: public; Owner: - --- - -CREATE MATERIALIZED VIEW public.blood_pressures_per_facility_per_days AS - WITH latest_bp_per_patient_per_day AS ( - SELECT DISTINCT ON (blood_pressures.facility_id, blood_pressures.patient_id, (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text, (date_part('doy'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text) blood_pressures.id AS bp_id, - blood_pressures.facility_id, - (date_part('doy'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS day, - (date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS month, - (date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS quarter, - (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS year - FROM (public.blood_pressures - JOIN public.medical_histories ON (((blood_pressures.patient_id = medical_histories.patient_id) AND (medical_histories.hypertension = 'yes'::text)))) - WHERE (blood_pressures.deleted_at IS NULL) - ORDER BY blood_pressures.facility_id, blood_pressures.patient_id, (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text, (date_part('doy'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text, blood_pressures.recorded_at DESC, blood_pressures.id - ) - SELECT count(latest_bp_per_patient_per_day.bp_id) AS bp_count, - facilities.id AS facility_id, - timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, facilities.deleted_at)) AS deleted_at, - latest_bp_per_patient_per_day.day, - latest_bp_per_patient_per_day.month, - latest_bp_per_patient_per_day.quarter, - latest_bp_per_patient_per_day.year - FROM (latest_bp_per_patient_per_day - JOIN public.facilities ON ((facilities.id = latest_bp_per_patient_per_day.facility_id))) - GROUP BY latest_bp_per_patient_per_day.day, latest_bp_per_patient_per_day.month, latest_bp_per_patient_per_day.quarter, latest_bp_per_patient_per_day.year, facilities.deleted_at, facilities.id - WITH NO DATA; - - -- -- Name: blood_sugars; Type: TABLE; Schema: public; Owner: - -- @@ -1554,36 +1533,84 @@ CREATE TABLE public.patients ( deleted_by_user_id uuid, deleted_reason character varying, assigned_facility_id uuid, + diagnosed_confirmed_at timestamp without time zone, eligible_for_reassignment text DEFAULT 'unknown'::text NOT NULL ); +-- +-- Name: blood_pressures_per_facility_per_days; Type: MATERIALIZED VIEW; Schema: public; Owner: - +-- + +CREATE MATERIALIZED VIEW public.blood_pressures_per_facility_per_days AS + WITH registered_patients AS ( + SELECT DISTINCT id + FROM public.patients + WHERE deleted_at IS NULL + AND diagnosed_confirmed_at IS NOT NULL + ), + latest_bp_per_patient_per_day AS ( + SELECT DISTINCT ON (blood_pressures.facility_id, blood_pressures.patient_id, (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text, (date_part('doy'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text) blood_pressures.id AS bp_id, + blood_pressures.facility_id, + (date_part('doy'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS day, + (date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS month, + (date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS quarter, + (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS year + FROM (public.blood_pressures + JOIN public.medical_histories ON (((blood_pressures.patient_id = medical_histories.patient_id) AND (medical_histories.hypertension = 'yes'::text))) + JOIN registered_patients ON registered_patients.id = blood_pressures.patient_id + ) + WHERE (blood_pressures.deleted_at IS NULL) + ORDER BY blood_pressures.facility_id, blood_pressures.patient_id, (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text, (date_part('doy'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text, blood_pressures.recorded_at DESC, blood_pressures.id +) + SELECT count(latest_bp_per_patient_per_day.bp_id) AS bp_count, + facilities.id AS facility_id, + timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, facilities.deleted_at)) AS deleted_at, + latest_bp_per_patient_per_day.day, + latest_bp_per_patient_per_day.month, + latest_bp_per_patient_per_day.quarter, + latest_bp_per_patient_per_day.year + FROM (latest_bp_per_patient_per_day + JOIN public.facilities ON ((facilities.id = latest_bp_per_patient_per_day.facility_id))) + GROUP BY latest_bp_per_patient_per_day.day, latest_bp_per_patient_per_day.month, latest_bp_per_patient_per_day.quarter, latest_bp_per_patient_per_day.year, facilities.deleted_at, facilities.id + WITH NO DATA; + -- -- Name: latest_blood_pressures_per_patient_per_months; Type: MATERIALIZED VIEW; Schema: public; Owner: - -- CREATE MATERIALIZED VIEW public.latest_blood_pressures_per_patient_per_months AS - SELECT DISTINCT ON (blood_pressures.patient_id, (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at))))::text, (date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at))))::text) blood_pressures.id AS bp_id, - blood_pressures.patient_id, - patients.registration_facility_id, - patients.assigned_facility_id, - patients.status AS patient_status, - blood_pressures.facility_id AS bp_facility_id, - timezone('UTC'::text, timezone('UTC'::text, blood_pressures.recorded_at)) AS bp_recorded_at, - timezone('UTC'::text, timezone('UTC'::text, patients.recorded_at)) AS patient_recorded_at, - blood_pressures.systolic, - blood_pressures.diastolic, - timezone('UTC'::text, timezone('UTC'::text, blood_pressures.deleted_at)) AS deleted_at, - medical_histories.hypertension AS medical_history_hypertension, - (date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at))))::text AS month, - (date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at))))::text AS quarter, - (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at))))::text AS year - FROM ((public.blood_pressures - JOIN public.patients ON ((patients.id = blood_pressures.patient_id))) - LEFT JOIN public.medical_histories ON ((medical_histories.patient_id = blood_pressures.patient_id))) - WHERE ((blood_pressures.deleted_at IS NULL) AND (patients.deleted_at IS NULL)) - ORDER BY blood_pressures.patient_id, (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at))))::text, (date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at))))::text, blood_pressures.recorded_at DESC, blood_pressures.id - WITH NO DATA; + WITH registered_patients AS ( + SELECT DISTINCT id, + registration_facility_id, + assigned_facility_id, + status, + recorded_at + FROM public.patients + WHERE deleted_at IS NULL + AND diagnosed_confirmed_at IS NOT NULL + ) + SELECT DISTINCT ON (blood_pressures.patient_id, (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at)))::text), (date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at)))::text)) + blood_pressures.id AS bp_id, + blood_pressures.patient_id AS patient_id, + registered_patients.registration_facility_id AS registration_facility_id, + registered_patients.assigned_facility_id AS assigned_facility_id, + registered_patients.status as patient_status, + blood_pressures.facility_id AS bp_facility_id, + timezone('UTC'::text, timezone('UTC'::text, blood_pressures.recorded_at)) AS bp_recorded_at, + timezone('UTC'::text, timezone('UTC'::text, registered_patients.recorded_at)) AS patient_recorded_at, + blood_pressures.systolic AS systolic, + blood_pressures.diastolic AS diastolic, + timezone('UTC'::text, timezone('UTC'::text, blood_pressures.deleted_at)) AS deleted_at, + medical_histories.hypertension as medical_history_hypertension, + date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at)))::text AS month, + date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at)))::text AS quarter, + date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at)))::text AS year + FROM public.blood_pressures JOIN registered_patients ON registered_patients.id = blood_pressures.patient_id + LEFT JOIN public.medical_histories ON medical_histories.patient_id = blood_pressures.patient_id + WHERE blood_pressures.deleted_at IS NULL + ORDER BY patient_id, year, month, blood_pressures.recorded_at DESC, bp_id + WITH NO DATA; -- @@ -2166,7 +2193,7 @@ CREATE MATERIALIZED VIEW public.materialized_patient_summaries AS LEFT JOIN latest_blood_pressures ON ((latest_blood_pressures.patient_id = p.id))) LEFT JOIN latest_blood_sugars ON ((latest_blood_sugars.patient_id = p.id))) LEFT JOIN next_scheduled_appointment ON ((next_scheduled_appointment.patient_id = p.id))) - WHERE (p.deleted_at IS NULL) + WHERE (p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) WITH NO DATA; @@ -2891,55 +2918,57 @@ COMMENT ON COLUMN public.reporting_facilities.organization_slug IS 'Human readab -- CREATE MATERIALIZED VIEW public.reporting_facility_appointment_scheduled_days AS - WITH latest_appointments_per_patient_per_month AS ( - SELECT DISTINCT ON (a.patient_id, (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, a.device_created_at)), 'YYYY-MM-01'::text))::date) a.id, - a.patient_id, - a.facility_id, - a.scheduled_date, - a.status, - a.cancel_reason, - a.device_created_at, - a.device_updated_at, - a.created_at, - a.updated_at, - a.remind_on, - a.agreed_to_visit, - a.deleted_at, - a.appointment_type, - a.user_id, - a.creation_facility_id, - mh.hypertension, - mh.diabetes, - (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, a.device_created_at)), 'YYYY-MM-01'::text))::date AS month_date - FROM ((public.appointments a - JOIN public.patients p ON ((p.id = a.patient_id))) - JOIN public.medical_histories mh ON ((mh.patient_id = a.patient_id))) - WHERE ((a.scheduled_date >= date_trunc('day'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, a.device_created_at)))) AND (a.device_created_at >= date_trunc('month'::text, (timezone('UTC'::text, now()) - '6 mons'::interval))) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, a.device_created_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at)))) AND (p.deleted_at IS NULL) AND (a.deleted_at IS NULL) AND ((mh.hypertension = 'yes'::text) OR (mh.diabetes = 'yes'::text))) - ORDER BY a.patient_id, (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, a.device_created_at)), 'YYYY-MM-01'::text))::date, a.device_created_at DESC - ), scheduled_days_distribution AS ( - SELECT latest_appointments_per_patient_per_month.month_date, - width_bucket((date_part('days'::text, ((latest_appointments_per_patient_per_month.scheduled_date)::timestamp without time zone - date_trunc('day'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, latest_appointments_per_patient_per_month.device_created_at))))))::integer, ARRAY[0, 15, 32, 63]) AS bucket, - count(*) AS number_of_appointments, - latest_appointments_per_patient_per_month.hypertension, - latest_appointments_per_patient_per_month.diabetes, - latest_appointments_per_patient_per_month.creation_facility_id AS facility_id - FROM latest_appointments_per_patient_per_month - GROUP BY (width_bucket((date_part('days'::text, ((latest_appointments_per_patient_per_month.scheduled_date)::timestamp without time zone - date_trunc('day'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, latest_appointments_per_patient_per_month.device_created_at))))))::integer, ARRAY[0, 15, 32, 63])), latest_appointments_per_patient_per_month.creation_facility_id, latest_appointments_per_patient_per_month.month_date, latest_appointments_per_patient_per_month.hypertension, latest_appointments_per_patient_per_month.diabetes - ) - SELECT scheduled_days_distribution.facility_id, - scheduled_days_distribution.month_date, - (sum(scheduled_days_distribution.number_of_appointments) FILTER (WHERE ((scheduled_days_distribution.bucket = 1) AND (scheduled_days_distribution.hypertension = 'yes'::text))))::integer AS htn_appts_scheduled_0_to_14_days, - (sum(scheduled_days_distribution.number_of_appointments) FILTER (WHERE ((scheduled_days_distribution.bucket = 2) AND (scheduled_days_distribution.hypertension = 'yes'::text))))::integer AS htn_appts_scheduled_15_to_31_days, - (sum(scheduled_days_distribution.number_of_appointments) FILTER (WHERE ((scheduled_days_distribution.bucket = 3) AND (scheduled_days_distribution.hypertension = 'yes'::text))))::integer AS htn_appts_scheduled_32_to_62_days, - (sum(scheduled_days_distribution.number_of_appointments) FILTER (WHERE ((scheduled_days_distribution.bucket = 4) AND (scheduled_days_distribution.hypertension = 'yes'::text))))::integer AS htn_appts_scheduled_more_than_62_days, - (sum(scheduled_days_distribution.number_of_appointments) FILTER (WHERE (scheduled_days_distribution.hypertension = 'yes'::text)))::integer AS htn_total_appts_scheduled, - (sum(scheduled_days_distribution.number_of_appointments) FILTER (WHERE ((scheduled_days_distribution.bucket = 1) AND (scheduled_days_distribution.diabetes = 'yes'::text))))::integer AS diabetes_appts_scheduled_0_to_14_days, - (sum(scheduled_days_distribution.number_of_appointments) FILTER (WHERE ((scheduled_days_distribution.bucket = 2) AND (scheduled_days_distribution.diabetes = 'yes'::text))))::integer AS diabetes_appts_scheduled_15_to_31_days, - (sum(scheduled_days_distribution.number_of_appointments) FILTER (WHERE ((scheduled_days_distribution.bucket = 3) AND (scheduled_days_distribution.diabetes = 'yes'::text))))::integer AS diabetes_appts_scheduled_32_to_62_days, - (sum(scheduled_days_distribution.number_of_appointments) FILTER (WHERE ((scheduled_days_distribution.bucket = 4) AND (scheduled_days_distribution.diabetes = 'yes'::text))))::integer AS diabetes_appts_scheduled_more_than_62_days, - (sum(scheduled_days_distribution.number_of_appointments) FILTER (WHERE (scheduled_days_distribution.diabetes = 'yes'::text)))::integer AS diabetes_total_appts_scheduled - FROM scheduled_days_distribution - GROUP BY scheduled_days_distribution.facility_id, scheduled_days_distribution.month_date + WITH latest_medical_histories AS ( + SELECT DISTINCT ON (patient_id) mh.* + FROM public.medical_histories mh + WHERE mh.deleted_at IS NULL + ORDER BY patient_id, mh.device_created_at DESC + ), + latest_appointments_per_patient_per_month AS ( + SELECT DISTINCT ON (patient_id, month_date) a.*, + lmh.hypertension, + lmh.diabetes, + to_char(a.device_created_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')), 'YYYY-MM-01')::date month_date + FROM public.appointments a + INNER JOIN public.patients p ON p.id = a.patient_id + INNER JOIN latest_medical_histories lmh ON lmh.patient_id = a.patient_id + WHERE a.scheduled_date >= date_trunc('day', a.device_created_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE'))) + AND a.device_created_at >= date_trunc('month', (now() AT TIME ZONE 'UTC') - INTERVAL '6 months') + AND date_trunc('month', a.device_created_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE'))) + > date_trunc('month', p.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE'))) + AND p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL + AND a.deleted_at IS NULL + AND (lmh.hypertension = 'yes' OR lmh.diabetes = 'yes') + ORDER BY a.patient_id, month_date, a.device_created_at desc + ), + scheduled_days_distribution AS ( + SELECT month_date, + width_bucket( + extract('days' FROM (scheduled_date - date_trunc('day', device_created_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))))::integer, + array[0, 15, 32, 63] + ) bucket, + COUNT(*) number_of_appointments, + hypertension, + diabetes, + creation_facility_id facility_id + FROM latest_appointments_per_patient_per_month + GROUP BY bucket, creation_facility_id, month_date, hypertension, diabetes) + + SELECT facility_id, + month_date, + (SUM(number_of_appointments) FILTER (WHERE bucket = 1 AND hypertension = 'yes'))::integer AS htn_appts_scheduled_0_to_14_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 2 AND hypertension = 'yes'))::integer AS htn_appts_scheduled_15_to_31_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 3 AND hypertension = 'yes'))::integer AS htn_appts_scheduled_32_to_62_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 4 AND hypertension = 'yes'))::integer AS htn_appts_scheduled_more_than_62_days, + (SUM(number_of_appointments) FILTER (WHERE hypertension = 'yes'))::integer htn_total_appts_scheduled, + + (SUM(number_of_appointments) FILTER (WHERE bucket = 1 AND diabetes = 'yes'))::integer AS diabetes_appts_scheduled_0_to_14_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 2 AND diabetes = 'yes'))::integer AS diabetes_appts_scheduled_15_to_31_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 3 AND diabetes = 'yes'))::integer AS diabetes_appts_scheduled_32_to_62_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 4 AND diabetes = 'yes'))::integer AS diabetes_appts_scheduled_more_than_62_days, + (SUM(number_of_appointments) FILTER (WHERE diabetes = 'yes'))::integer diabetes_total_appts_scheduled + FROM scheduled_days_distribution + GROUP BY facility_id, month_date WITH NO DATA; @@ -2958,8 +2987,8 @@ CREATE MATERIALIZED VIEW public.reporting_facility_daily_follow_ups_and_registra bp.recorded_at AS visited_at, (EXTRACT(doy FROM ((bp.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year FROM (public.patients p - JOIN public.blood_pressures bp ON (((p.id = bp.patient_id) AND (date_trunc('day'::text, ((bp.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) - WHERE ((p.deleted_at IS NULL) AND (bp.recorded_at > (CURRENT_TIMESTAMP - '30 days'::interval))) + JOIN public.blood_pressures bp ON (((p.id = bp.patient_id) AND (date_trunc('day'::text, ((bp.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.diagnosed_confirmed_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) + WHERE ((p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) AND (bp.recorded_at > (CURRENT_TIMESTAMP - '30 days'::interval))) ), follow_up_blood_sugars AS ( SELECT DISTINCT ON (((EXTRACT(doy FROM ((bs.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), bs.facility_id, p.id) p.id AS patient_id, (p.gender)::public.gender_enum AS patient_gender, @@ -2970,8 +2999,8 @@ CREATE MATERIALIZED VIEW public.reporting_facility_daily_follow_ups_and_registra bs.recorded_at AS visited_at, (EXTRACT(doy FROM ((bs.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year FROM (public.patients p - JOIN public.blood_sugars bs ON (((p.id = bs.patient_id) AND (date_trunc('day'::text, ((bs.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) - WHERE ((p.deleted_at IS NULL) AND (bs.recorded_at > (CURRENT_TIMESTAMP - '30 days'::interval))) + JOIN public.blood_sugars bs ON (((p.id = bs.patient_id) AND (date_trunc('day'::text, ((bs.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.diagnosed_confirmed_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) + WHERE ((p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) AND (bs.recorded_at > (CURRENT_TIMESTAMP - '30 days'::interval))) ), follow_up_prescription_drugs AS ( SELECT DISTINCT ON (((EXTRACT(doy FROM ((pd.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), pd.facility_id, p.id) p.id AS patient_id, (p.gender)::public.gender_enum AS patient_gender, @@ -2982,8 +3011,8 @@ CREATE MATERIALIZED VIEW public.reporting_facility_daily_follow_ups_and_registra pd.device_created_at AS visited_at, (EXTRACT(doy FROM ((pd.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year FROM (public.patients p - JOIN public.prescription_drugs pd ON (((p.id = pd.patient_id) AND (date_trunc('day'::text, ((pd.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) - WHERE ((p.deleted_at IS NULL) AND (pd.device_created_at > (CURRENT_TIMESTAMP - '30 days'::interval))) + JOIN public.prescription_drugs pd ON (((p.id = pd.patient_id) AND (date_trunc('day'::text, ((pd.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.diagnosed_confirmed_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) + WHERE ((p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) AND (pd.device_created_at > (CURRENT_TIMESTAMP - '30 days'::interval))) ), follow_up_appointments AS ( SELECT DISTINCT ON (((EXTRACT(doy FROM ((app.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), app.creation_facility_id, p.id) p.id AS patient_id, (p.gender)::public.gender_enum AS patient_gender, @@ -2994,19 +3023,19 @@ CREATE MATERIALIZED VIEW public.reporting_facility_daily_follow_ups_and_registra app.device_created_at AS visited_at, (EXTRACT(doy FROM ((app.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year FROM (public.patients p - JOIN public.appointments app ON (((p.id = app.patient_id) AND (date_trunc('day'::text, ((app.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) - WHERE ((p.deleted_at IS NULL) AND (app.device_created_at > (CURRENT_TIMESTAMP - '30 days'::interval))) + JOIN public.appointments app ON (((p.id = app.patient_id) AND (date_trunc('day'::text, ((app.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.diagnosed_confirmed_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) + WHERE ((p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) AND (app.device_created_at > (CURRENT_TIMESTAMP - '30 days'::interval))) ), registered_patients AS ( - SELECT DISTINCT ON (((EXTRACT(doy FROM ((p.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), p.registration_facility_id, p.id) p.id AS patient_id, + SELECT DISTINCT ON (((EXTRACT(doy FROM ((p.diagnosed_confirmed_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), p.registration_facility_id, p.id) p.id AS patient_id, (p.gender)::public.gender_enum AS patient_gender, p.id AS visit_id, 'Registration'::text AS visit_type, p.registration_facility_id AS facility_id, p.registration_user_id AS user_id, - p.recorded_at AS visited_at, - (EXTRACT(doy FROM ((p.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year + p.diagnosed_confirmed_at AS visited_at, + (EXTRACT(doy FROM ((p.diagnosed_confirmed_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year FROM public.patients p - WHERE ((p.deleted_at IS NULL) AND (p.recorded_at > (CURRENT_TIMESTAMP - '30 days'::interval))) + WHERE ((p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) AND (p.diagnosed_confirmed_at > (CURRENT_TIMESTAMP - '30 days'::interval))) ), all_follow_ups AS ( SELECT follow_up_blood_pressures.patient_id, follow_up_blood_pressures.patient_gender, @@ -3240,16 +3269,16 @@ CREATE MATERIALIZED VIEW public.reporting_patient_blood_pressures AS bp.systolic, bp.diastolic, bp.facility_id AS blood_pressure_facility_id, - timezone('UTC'::text, timezone('UTC'::text, p.recorded_at)) AS patient_registered_at, + timezone('UTC'::text, timezone('UTC'::text, p.diagnosed_confirmed_at)) AS patient_registered_at, p.assigned_facility_id AS patient_assigned_facility_id, p.registration_facility_id AS patient_registration_facility_id, - (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at)))) * (12)::double precision) + (cal.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at))))) AS months_since_registration, - (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at)))) * (4)::double precision) + (cal.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at))))) AS quarters_since_registration, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at)))) * (12)::double precision) + (cal.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))) AS months_since_registration, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at)))) * (4)::double precision) + (cal.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))) AS quarters_since_registration, (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)))) * (12)::double precision) + (cal.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at))))) AS months_since_bp, (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)))) * (4)::double precision) + (cal.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at))))) AS quarters_since_bp FROM ((public.blood_pressures bp LEFT JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)), 'YYYY-MM'::text) <= to_char((cal.month_date)::timestamp with time zone, 'YYYY-MM'::text)))) - JOIN public.patients p ON (((bp.patient_id = p.id) AND (p.deleted_at IS NULL)))) + JOIN public.patients p ON (((bp.patient_id = p.id) AND (p.deleted_at IS NULL) AND (p.diagnosed_confirmed_at IS NOT NULL)))) WHERE (bp.deleted_at IS NULL) ORDER BY bp.patient_id, cal.month_date, bp.recorded_at DESC WITH NO DATA; @@ -3296,16 +3325,16 @@ CREATE MATERIALIZED VIEW public.reporting_patient_blood_sugars AS END ELSE NULL::text END AS blood_sugar_risk_state, - timezone('UTC'::text, timezone('UTC'::text, p.recorded_at)) AS patient_registered_at, + timezone('UTC'::text, timezone('UTC'::text, p.diagnosed_confirmed_at)) AS patient_registered_at, p.assigned_facility_id AS patient_assigned_facility_id, p.registration_facility_id AS patient_registration_facility_id, - (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at)))) * (12)::double precision) + (cal.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at))))) AS months_since_registration, - (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at)))) * (4)::double precision) + (cal.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at))))) AS quarters_since_registration, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at)))) * (12)::double precision) + (cal.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))) AS months_since_registration, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at)))) * (4)::double precision) + (cal.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))) AS quarters_since_registration, (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)))) * (12)::double precision) + (cal.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at))))) AS months_since_bs, (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)))) * (4)::double precision) + (cal.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at))))) AS quarters_since_bs FROM ((public.blood_sugars bs LEFT JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)), 'YYYY-MM'::text) <= to_char((cal.month_date)::timestamp with time zone, 'YYYY-MM'::text)))) - JOIN public.patients p ON (((bs.patient_id = p.id) AND (p.deleted_at IS NULL)))) + JOIN public.patients p ON (((bs.patient_id = p.id) AND (p.deleted_at IS NULL) AND (p.diagnosed_confirmed_at IS NOT NULL)))) WHERE (bs.deleted_at IS NULL) ORDER BY bs.patient_id, cal.month_date, bs.recorded_at DESC WITH NO DATA; @@ -3326,8 +3355,8 @@ CREATE MATERIALIZED VIEW public.reporting_patient_follow_ups AS bp.recorded_at AS visited_at, to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)), 'YYYY-MM'::text) AS month_string FROM (public.patients p - JOIN public.blood_pressures bp ON (((p.id = bp.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at))))))) - WHERE (p.deleted_at IS NULL) + JOIN public.blood_pressures bp ON (((p.id = bp.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))))) + WHERE (p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) ), follow_up_blood_sugars AS ( SELECT DISTINCT ON (p.id, bs.facility_id, bs.user_id, (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)), 'YYYY-MM'::text))) p.id AS patient_id, (p.gender)::public.gender_enum AS patient_gender, @@ -3338,8 +3367,8 @@ CREATE MATERIALIZED VIEW public.reporting_patient_follow_ups AS bs.recorded_at AS visited_at, to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)), 'YYYY-MM'::text) AS month_string FROM (public.patients p - JOIN public.blood_sugars bs ON (((p.id = bs.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at))))))) - WHERE (p.deleted_at IS NULL) + JOIN public.blood_sugars bs ON (((p.id = bs.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))))) + WHERE (p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) ), follow_up_prescription_drugs AS ( SELECT DISTINCT ON (p.id, pd.facility_id, pd.user_id, (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, pd.device_created_at)), 'YYYY-MM'::text))) p.id AS patient_id, (p.gender)::public.gender_enum AS patient_gender, @@ -3350,8 +3379,8 @@ CREATE MATERIALIZED VIEW public.reporting_patient_follow_ups AS pd.device_created_at AS visited_at, to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, pd.device_created_at)), 'YYYY-MM'::text) AS month_string FROM (public.patients p - JOIN public.prescription_drugs pd ON (((p.id = pd.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, pd.device_created_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at))))))) - WHERE (p.deleted_at IS NULL) + JOIN public.prescription_drugs pd ON (((p.id = pd.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, pd.device_created_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))))) + WHERE (p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) ), follow_up_appointments AS ( SELECT DISTINCT ON (p.id, app.creation_facility_id, app.user_id, (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, app.device_created_at)), 'YYYY-MM'::text))) p.id AS patient_id, (p.gender)::public.gender_enum AS patient_gender, @@ -3362,8 +3391,8 @@ CREATE MATERIALIZED VIEW public.reporting_patient_follow_ups AS app.device_created_at AS visited_at, to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, app.device_created_at)), 'YYYY-MM'::text) AS month_string FROM (public.patients p - JOIN public.appointments app ON (((p.id = app.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, app.device_created_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.recorded_at))))))) - WHERE (p.deleted_at IS NULL) + JOIN public.appointments app ON (((p.id = app.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, app.device_created_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))))) + WHERE (p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) ), all_follow_ups AS ( SELECT follow_up_blood_pressures.patient_id, follow_up_blood_pressures.patient_gender, @@ -3441,7 +3470,7 @@ CREATE MATERIALIZED VIEW public.reporting_patient_visits AS p.quarter_string, p.assigned_facility_id, p.registration_facility_id, - timezone('UTC'::text, timezone('UTC'::text, p.recorded_at)) AS patient_recorded_at, + timezone('UTC'::text, timezone('UTC'::text, p.diagnosed_confirmed_at)) AS patient_recorded_at, e.id AS encounter_id, e.facility_id AS encounter_facility_id, timezone('UTC'::text, timezone('UTC'::text, e.recorded_at)) AS encounter_recorded_at, @@ -3465,8 +3494,8 @@ CREATE MATERIALIZED VIEW public.reporting_patient_visits AS ELSE NULL::uuid END], NULL::uuid) AS visited_facility_ids, timezone('UTC'::text, timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at))) AS visited_at, - (((p.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.recorded_at)))) * (12)::double precision) + (p.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.recorded_at))))) AS months_since_registration, - (((p.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.recorded_at)))) * (4)::double precision) + (p.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.recorded_at))))) AS quarters_since_registration, + (((p.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.diagnosed_confirmed_at)))) * (12)::double precision) + (p.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.diagnosed_confirmed_at))))) AS months_since_registration, + (((p.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.diagnosed_confirmed_at)))) * (4)::double precision) + (p.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.diagnosed_confirmed_at))))) AS quarters_since_registration, (((p.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at))))) * (12)::double precision) + (p.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at)))))) AS months_since_visit, (((p.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at))))) * (4)::double precision) + (p.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at)))))) AS quarters_since_visit FROM (((( SELECT p_1.id, @@ -3492,6 +3521,7 @@ CREATE MATERIALIZED VIEW public.reporting_patient_visits AS p_1.deleted_by_user_id, p_1.deleted_reason, p_1.assigned_facility_id, + p_1.diagnosed_confirmed_at, cal.month_date, cal.month, cal.quarter, @@ -3499,7 +3529,7 @@ CREATE MATERIALIZED VIEW public.reporting_patient_visits AS cal.month_string, cal.quarter_string FROM (public.patients p_1 - LEFT JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p_1.recorded_at)), 'YYYY-MM'::text) <= cal.month_string)))) p + LEFT JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p_1.diagnosed_confirmed_at)), 'YYYY-MM'::text) <= cal.month_string)))) p LEFT JOIN LATERAL ( SELECT timezone('UTC'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), (encounters.encountered_on)::timestamp without time zone)) AS recorded_at, encounters.id, encounters.facility_id, @@ -3560,7 +3590,7 @@ CREATE MATERIALIZED VIEW public.reporting_patient_visits AS WHERE ((appointments.patient_id = p.id) AND (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, appointments.device_created_at)), 'YYYY-MM'::text) <= p.month_string) AND (appointments.deleted_at IS NULL)) ORDER BY appointments.device_created_at DESC LIMIT 1) app ON (true)) - WHERE (p.deleted_at IS NULL) + WHERE (p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) ORDER BY p.id, p.month_date, (timezone('UTC'::text, timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at)))) DESC WITH NO DATA; @@ -3610,8 +3640,8 @@ CREATE MATERIALIZED VIEW public.reporting_prescriptions AS cal.month_string, cal.quarter_string FROM (public.patients p_1 - LEFT JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p_1.recorded_at)), 'YYYY-MM'::text) <= cal.month_string))) - WHERE (p_1.deleted_at IS NULL)) p + LEFT JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p_1.diagnosed_confirmed_at)), 'YYYY-MM'::text) <= cal.month_string))) + WHERE (p_1.deleted_at IS NULL AND p_1.diagnosed_confirmed_at IS NOT NULL)) p LEFT JOIN LATERAL ( SELECT DISTINCT ON (clean.medicine) actual.name AS actual_name, actual.dosage AS actual_dosage, clean.medicine AS clean_name, @@ -3632,7 +3662,7 @@ CREATE MATERIALIZED VIEW public.reporting_prescriptions AS -- Name: reporting_patient_states; Type: MATERIALIZED VIEW; Schema: public; Owner: - -- -CREATE VIEW public.reporting_patient_states AS SELECT * FROM simple_reporting.reporting_patient_states; +CREATE OR REPLACE VIEW public.reporting_patient_states AS SELECT * FROM simple_reporting.reporting_patient_states; -- -- Name: VIEW reporting_patient_states; Type: COMMENT; Schema: public; Owner: - @@ -4366,6 +4396,7 @@ CREATE MATERIALIZED VIEW public.reporting_overdue_calls AS FROM (((public.call_results cr JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, cr.device_created_at)), 'YYYY-MM'::text) = to_char((cal.month_date)::timestamp with time zone, 'YYYY-MM'::text)))) JOIN public.appointments a ON (((cr.appointment_id = a.id) AND (a.deleted_at IS NULL)))) + JOIN public.patients p ON (p.id = a.patient_id AND ((p.deleted_at IS NULL) AND (p.diagnosed_confirmed_at IS NOT NULL))) JOIN public.reporting_facilities appointment_facility ON ((a.facility_id = appointment_facility.facility_id))) WHERE (cr.deleted_at IS NULL) ORDER BY a.patient_id, cal.month_date, cr.device_created_at DESC @@ -7570,6 +7601,13 @@ CREATE INDEX index_patients_on_id_and_updated_at ON public.patients USING btree CREATE INDEX index_patients_on_recorded_at ON public.patients USING btree (recorded_at); +-- +-- Name: index_patients_on_diagnosed_confirmed_at; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX index_patients_on_diagnosed_confirmed_at ON public.patients USING btree (diagnosed_confirmed_at); + + -- -- Name: index_patients_on_registration_facility_id; Type: INDEX; Schema: public; Owner: - -- @@ -8597,5 +8635,20 @@ INSERT INTO "schema_migrations" (version) VALUES ('20250924102156'), ('20250925094123'), ('20251125090819'), -('20251211073126'); - +('20251211073126'), +('20251112085230'), +('20251112091000'), +('20251119095624'), +('20251119133109'), +('20251120141950'), +('20251126052630'), +('20251127104720'), +('20251201094315'), +('20251202062322'), +('20251203093958'), +('20251204092000'), +('20251205091911'), +('20251208104102'), +('20251210061204'), +('20251211154907'), +('20251215113615'); diff --git a/db/views/blood_pressures_per_facility_per_days_v03.sql b/db/views/blood_pressures_per_facility_per_days_v03.sql new file mode 100644 index 0000000000..369393ffef --- /dev/null +++ b/db/views/blood_pressures_per_facility_per_days_v03.sql @@ -0,0 +1,30 @@ +WITH registered_patients AS ( + SELECT DISTINCT id + FROM public.patients + WHERE deleted_at IS NULL + AND diagnosed_confirmed_at IS NOT NULL + ), +latest_bp_per_patient_per_day AS ( + SELECT DISTINCT ON (blood_pressures.facility_id, blood_pressures.patient_id, (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text, (date_part('doy'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text) blood_pressures.id AS bp_id, + blood_pressures.facility_id, + (date_part('doy'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS day, + (date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS month, + (date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS quarter, + (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text AS year + FROM (public.blood_pressures + JOIN public.medical_histories ON (((blood_pressures.patient_id = medical_histories.patient_id) AND (medical_histories.hypertension = 'yes'::text))) + JOIN registered_patients ON registered_patients.id = blood_pressures.patient_id + ) + WHERE (blood_pressures.deleted_at IS NULL) + ORDER BY blood_pressures.facility_id, blood_pressures.patient_id, (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text, (date_part('doy'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at))))::text, blood_pressures.recorded_at DESC, blood_pressures.id +) +SELECT count(latest_bp_per_patient_per_day.bp_id) AS bp_count, + facilities.id AS facility_id, + timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, facilities.deleted_at)) AS deleted_at, + latest_bp_per_patient_per_day.day, + latest_bp_per_patient_per_day.month, + latest_bp_per_patient_per_day.quarter, + latest_bp_per_patient_per_day.year + FROM (latest_bp_per_patient_per_day + JOIN public.facilities ON ((facilities.id = latest_bp_per_patient_per_day.facility_id))) +GROUP BY latest_bp_per_patient_per_day.day, latest_bp_per_patient_per_day.month, latest_bp_per_patient_per_day.quarter, latest_bp_per_patient_per_day.year, facilities.deleted_at, facilities.id; \ No newline at end of file diff --git a/db/views/latest_blood_pressures_per_patient_per_months_v07.sql b/db/views/latest_blood_pressures_per_patient_per_months_v07.sql new file mode 100644 index 0000000000..edbefe7e04 --- /dev/null +++ b/db/views/latest_blood_pressures_per_patient_per_months_v07.sql @@ -0,0 +1,30 @@ +WITH registered_patients AS ( + SELECT DISTINCT id, + registration_facility_id, + assigned_facility_id, + status, + recorded_at + FROM public.patients + WHERE deleted_at IS NULL + AND diagnosed_confirmed_at IS NOT NULL +) +SELECT DISTINCT ON (blood_pressures.patient_id, (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at)))::text), (date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at)))::text)) + blood_pressures.id AS bp_id, + blood_pressures.patient_id AS patient_id, + registered_patients.registration_facility_id AS registration_facility_id, + registered_patients.assigned_facility_id AS assigned_facility_id, + registered_patients.status as patient_status, + blood_pressures.facility_id AS bp_facility_id, + timezone('UTC'::text, timezone('UTC'::text, blood_pressures.recorded_at)) AS bp_recorded_at, + timezone('UTC'::text, timezone('UTC'::text, registered_patients.recorded_at)) AS patient_recorded_at, + blood_pressures.systolic AS systolic, + blood_pressures.diastolic AS diastolic, + timezone('UTC'::text, timezone('UTC'::text, blood_pressures.deleted_at)) AS deleted_at, + medical_histories.hypertension as medical_history_hypertension, + date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at)))::text AS month, + date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at)))::text AS quarter, + date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, blood_pressures.recorded_at)))::text AS year +FROM public.blood_pressures JOIN registered_patients ON registered_patients.id = blood_pressures.patient_id +LEFT JOIN public.medical_histories ON medical_histories.patient_id = blood_pressures.patient_id +WHERE blood_pressures.deleted_at IS NULL +ORDER BY patient_id, year, month, blood_pressures.recorded_at DESC, bp_id; \ No newline at end of file diff --git a/db/views/materialized_patient_summaries_v04.sql b/db/views/materialized_patient_summaries_v04.sql index 54ba8dd889..c283f1f734 100644 --- a/db/views/materialized_patient_summaries_v04.sql +++ b/db/views/materialized_patient_summaries_v04.sql @@ -1,359 +1,339 @@ -with latest_bp_passport as ( - select - distinct on (patient_id) - id, - identifier, - patient_id - from patient_business_identifiers - where identifier_type = 'simple_bp_passport' and deleted_at is null - order by patient_id, device_created_at desc -), latest_phone_number as ( - select - distinct on (patient_id) +WITH latest_bp_passport AS ( + SELECT DISTINCT ON (patient_business_identifiers.patient_id) patient_business_identifiers.id, + patient_business_identifiers.identifier, + patient_business_identifiers.identifier_type, + patient_business_identifiers.patient_id, + patient_business_identifiers.metadata_version, + patient_business_identifiers.metadata, + patient_business_identifiers.device_created_at, + patient_business_identifiers.device_updated_at, + patient_business_identifiers.deleted_at, + patient_business_identifiers.created_at, + patient_business_identifiers.updated_at + FROM public.patient_business_identifiers + WHERE (((patient_business_identifiers.identifier_type)::text = 'simple_bp_passport'::text) AND (patient_business_identifiers.deleted_at IS NULL)) + ORDER BY patient_business_identifiers.patient_id, patient_business_identifiers.device_created_at DESC + ), latest_phone_number AS ( + SELECT DISTINCT ON (patient_phone_numbers.patient_id) patient_phone_numbers.id, + patient_phone_numbers.number, + patient_phone_numbers.phone_type, + patient_phone_numbers.active, + patient_phone_numbers.created_at, + patient_phone_numbers.updated_at, patient_phone_numbers.patient_id, - patient_phone_numbers.number - from patient_phone_numbers - where deleted_at is null - order by patient_id, device_created_at desc -), latest_medical_history as ( - SELECT - DISTINCT ON (patient_id) - patient_id, - hypertension, - diabetes - FROM - medical_histories - WHERE - deleted_at IS NULL -),ranked_prescription_drugs as ( - select - bp.id as bp_id, - array_agg(array[name, dosage] order by prescription_drugs.is_protocol_drug desc, prescription_drugs.name, prescription_drugs.device_created_at desc) as blood_pressure_drugs, - array_agg(name || '-' || dosage order by prescription_drugs.is_protocol_drug desc, prescription_drugs.name, prescription_drugs.device_created_at desc) as drug_strings - from blood_pressures bp - join prescription_drugs - on prescription_drugs.patient_id = bp.patient_id - and date(prescription_drugs.device_created_at) <= date(bp.recorded_at) - and (prescription_drugs.is_deleted is false or (prescription_drugs.is_deleted is true and date(prescription_drugs.device_updated_at) > date(bp.recorded_at))) - where bp.deleted_at is null and prescription_drugs.deleted_at is null - group by bp.id -), blood_pressure_follow_up as ( - select - distinct on (bp.patient_id, date(bp.recorded_at)) - bp.id bp_id, + patient_phone_numbers.device_created_at, + patient_phone_numbers.device_updated_at, + patient_phone_numbers.deleted_at, + patient_phone_numbers.dnd_status + FROM public.patient_phone_numbers + WHERE (patient_phone_numbers.deleted_at IS NULL) + ORDER BY patient_phone_numbers.patient_id, patient_phone_numbers.device_created_at DESC + ), latest_medical_history AS ( + SELECT DISTINCT ON (medical_histories.patient_id) medical_histories.id, + medical_histories.patient_id, + medical_histories.prior_heart_attack_boolean, + medical_histories.prior_stroke_boolean, + medical_histories.chronic_kidney_disease_boolean, + medical_histories.receiving_treatment_for_hypertension_boolean, + medical_histories.diabetes_boolean, + medical_histories.device_created_at, + medical_histories.device_updated_at, + medical_histories.created_at, + medical_histories.updated_at, + medical_histories.diagnosed_with_hypertension_boolean, + medical_histories.prior_heart_attack, + medical_histories.prior_stroke, + medical_histories.chronic_kidney_disease, + medical_histories.receiving_treatment_for_hypertension, + medical_histories.diabetes, + medical_histories.diagnosed_with_hypertension, + medical_histories.deleted_at, + medical_histories.user_id, + medical_histories.hypertension, + medical_histories.receiving_treatment_for_diabetes, + medical_histories.smoking, + medical_histories.cholesterol + FROM public.medical_histories + WHERE (medical_histories.deleted_at IS NULL) + ), ranked_prescription_drugs AS ( + SELECT bp.id AS bp_id, + array_agg(ARRAY[prescription_drugs.name, prescription_drugs.dosage] ORDER BY prescription_drugs.is_protocol_drug DESC, prescription_drugs.name, prescription_drugs.device_created_at DESC) AS blood_pressure_drugs, + array_agg((((prescription_drugs.name)::text || '-'::text) || (prescription_drugs.dosage)::text) ORDER BY prescription_drugs.is_protocol_drug DESC, prescription_drugs.name, prescription_drugs.device_created_at DESC) AS drug_strings + FROM (public.blood_pressures bp + JOIN public.prescription_drugs ON (((prescription_drugs.patient_id = bp.patient_id) AND (date(prescription_drugs.device_created_at) <= date(bp.recorded_at)) AND ((prescription_drugs.is_deleted IS FALSE) OR ((prescription_drugs.is_deleted IS TRUE) AND (date(prescription_drugs.device_updated_at) > date(bp.recorded_at))))))) + WHERE ((bp.deleted_at IS NULL) AND (prescription_drugs.deleted_at IS NULL)) + GROUP BY bp.id + ), blood_pressure_follow_up AS ( + SELECT DISTINCT ON (bp.patient_id, (date(bp.recorded_at))) bp.id AS bp_id, + appointments.id, + appointments.patient_id, + appointments.facility_id, appointments.scheduled_date, + appointments.status, + appointments.cancel_reason, appointments.device_created_at, - appointments.facility_id, - appointments.deleted_at - from blood_pressures bp - join appointments on appointments.patient_id = bp.patient_id and date(appointments.device_created_at) = date(bp.recorded_at) - order by bp.patient_id, date(bp.recorded_at), appointments.device_created_at desc -), blood_sugar_follow_up as ( - select - distinct on (bs.patient_id, date(bs.recorded_at)) - bs.id bs_id, + appointments.device_updated_at, + appointments.created_at, + appointments.updated_at, + appointments.remind_on, + appointments.agreed_to_visit, + appointments.deleted_at, + appointments.appointment_type, + appointments.user_id, + appointments.creation_facility_id + FROM (public.blood_pressures bp + JOIN public.appointments ON (((appointments.patient_id = bp.patient_id) AND (date(appointments.device_created_at) = date(bp.recorded_at))))) + ORDER BY bp.patient_id, (date(bp.recorded_at)), appointments.device_created_at DESC + ), blood_sugar_follow_up AS ( + SELECT DISTINCT ON (bs.patient_id, (date(bs.recorded_at))) bs.id AS bs_id, + appointments.id, + appointments.patient_id, + appointments.facility_id, appointments.scheduled_date, + appointments.status, + appointments.cancel_reason, appointments.device_created_at, - appointments.facility_id, - appointments.deleted_at - from blood_sugars bs - join appointments on appointments.patient_id = bs.patient_id and date(appointments.device_created_at) = date(bs.recorded_at) - order by bs.patient_id, date(bs.recorded_at), appointments.device_created_at desc -), ranked_blood_pressures as ( - select - bp.id, + appointments.device_updated_at, + appointments.created_at, + appointments.updated_at, + appointments.remind_on, + appointments.agreed_to_visit, + appointments.deleted_at, + appointments.appointment_type, + appointments.user_id, + appointments.creation_facility_id + FROM (public.blood_sugars bs + JOIN public.appointments ON (((appointments.patient_id = bs.patient_id) AND (date(appointments.device_created_at) = date(bs.recorded_at))))) + ORDER BY bs.patient_id, (date(bs.recorded_at)), appointments.device_created_at DESC + ), ranked_blood_pressures AS ( + SELECT bp.id, bp.patient_id, - bp.recorded_at as recorded_at, + bp.recorded_at, bp.systolic, bp.diastolic, - f.name as facility_name, + f.name AS facility_name, f.facility_type, f.district, f.state, - follow_up_facility.name as follow_up_facility_name, - a.scheduled_date as follow_up_date, - greatest(0, date_part('day', a.scheduled_date - date_trunc('day', a.device_created_at)))::int as follow_up_days, - - bp_drugs.blood_pressure_drugs[1][1] as prescription_drug_1_name, - bp_drugs.blood_pressure_drugs[1][2] as prescription_drug_1_dosage, - - bp_drugs.blood_pressure_drugs[2][1] as prescription_drug_2_name, - bp_drugs.blood_pressure_drugs[2][2] as prescription_drug_2_dosage, - - bp_drugs.blood_pressure_drugs[3][1] as prescription_drug_3_name, - bp_drugs.blood_pressure_drugs[3][2] as prescription_drug_3_dosage, - - bp_drugs.blood_pressure_drugs[4][1] as prescription_drug_4_name, - bp_drugs.blood_pressure_drugs[4][2] as prescription_drug_4_dosage, - - bp_drugs.blood_pressure_drugs[5][1] as prescription_drug_5_name, - bp_drugs.blood_pressure_drugs[5][2] as prescription_drug_5_dosage, - - (select string_agg(value, ', ') from unnest(bp_drugs.drug_strings[6:]) as value) as other_prescription_drugs, - (select string_agg(value, ', ') from unnest(bp_drugs.drug_strings) as value) as all_prescription_drugs, - - rank() over (partition by bp.patient_id order by bp.recorded_at desc, bp.id) rank - from blood_pressures bp - left outer join facilities f on bp.facility_id = f.id - left outer join ranked_prescription_drugs bp_drugs on bp.id = bp_drugs.bp_id - left outer join blood_pressure_follow_up a on a.bp_id = bp.id - left outer join facilities follow_up_facility on follow_up_facility.id = a.facility_id - where bp.deleted_at is null and a.deleted_at is null -), filtered_ranked_blood_pressures as ( - select - rbp.id, - rbp.patient_id, - rbp.recorded_at as recorded_at, - rbp.systolic, - rbp.diastolic, - rbp.facility_name, - rbp.facility_type, - rbp.district, - rbp.state, - rbp.follow_up_facility_name, - rbp.follow_up_date, - rbp.follow_up_days, - - rbp.prescription_drug_1_name, - rbp.prescription_drug_1_dosage, - rbp.prescription_drug_2_name, - rbp.prescription_drug_2_dosage, - rbp.prescription_drug_3_name, - rbp.prescription_drug_3_dosage, - rbp.prescription_drug_4_name, - rbp.prescription_drug_4_dosage, - rbp.prescription_drug_5_name, - rbp.prescription_drug_5_dosage, - - rbp.other_prescription_drugs, - rbp.all_prescription_drugs, - - case when rank = 2 and all_prescription_drugs != lag(all_prescription_drugs) over (partition by patient_id order by rank) then 1 else null end as the_medication_1_updated, - case when rank = 3 and all_prescription_drugs != lag(all_prescription_drugs) over (partition by patient_id order by rank) then 1 else null end as the_medication_2_updated, - case when rank = 4 and all_prescription_drugs != lag(all_prescription_drugs) over (partition by patient_id order by rank) then 1 else null end as the_medication_3_updated, - - rbp.rank - from ranked_blood_pressures rbp - where rank <= 4 -), latest_blood_pressures as ( - select - patient_id, - - max(case when rank = 1 then id::text end) as latest_blood_pressure_1_id, - max(case when rank = 1 then recorded_at end) as latest_blood_pressure_1_recorded_at, - max(case when rank = 1 then systolic end) as latest_blood_pressure_1_systolic, - max(case when rank = 1 then diastolic end) as latest_blood_pressure_1_diastolic, - max(case when rank = 1 then facility_name end) as latest_blood_pressure_1_facility_name, - max(case when rank = 1 then facility_type end) as latest_blood_pressure_1_facility_type, - max(case when rank = 1 then district end) as latest_blood_pressure_1_district, - max(case when rank = 1 then state end) as latest_blood_pressure_1_state, - max(case when rank = 1 then follow_up_facility_name end) as latest_blood_pressure_1_follow_up_facility_name, - max(case when rank = 1 then follow_up_date end) as latest_blood_pressure_1_follow_up_date, - max(case when rank = 1 then follow_up_days end) as latest_blood_pressure_1_follow_up_days, - the_medication_1_updated as latest_blood_pressure_1_medication_updated, - max(case when rank = 1 then prescription_drug_1_name end) as latest_blood_pressure_1_prescription_drug_1_name, - max(case when rank = 1 then prescription_drug_1_dosage end) as latest_blood_pressure_1_prescription_drug_1_dosage, - max(case when rank = 1 then prescription_drug_2_name end) as latest_blood_pressure_1_prescription_drug_2_name, - max(case when rank = 1 then prescription_drug_2_dosage end) as latest_blood_pressure_1_prescription_drug_2_dosage, - max(case when rank = 1 then prescription_drug_3_name end) as latest_blood_pressure_1_prescription_drug_3_name, - max(case when rank = 1 then prescription_drug_3_dosage end) as latest_blood_pressure_1_prescription_drug_3_dosage, - max(case when rank = 1 then prescription_drug_4_name end) as latest_blood_pressure_1_prescription_drug_4_name, - max(case when rank = 1 then prescription_drug_4_dosage end) as latest_blood_pressure_1_prescription_drug_4_dosage, - max(case when rank = 1 then prescription_drug_5_name end) as latest_blood_pressure_1_prescription_drug_5_name, - max(case when rank = 1 then prescription_drug_5_dosage end) as latest_blood_pressure_1_prescription_drug_5_dosage, - max(case when rank = 1 then other_prescription_drugs end) as latest_blood_pressure_1_other_prescription_drugs, - - max(case when rank = 2 then id::text end) as latest_blood_pressure_2_id, - max(case when rank = 2 then recorded_at end) as latest_blood_pressure_2_recorded_at, - max(case when rank = 2 then systolic end) as latest_blood_pressure_2_systolic, - max(case when rank = 2 then diastolic end) as latest_blood_pressure_2_diastolic, - max(case when rank = 2 then facility_name end) as latest_blood_pressure_2_facility_name, - max(case when rank = 2 then facility_type end) as latest_blood_pressure_2_facility_type, - max(case when rank = 2 then district end) as latest_blood_pressure_2_district, - max(case when rank = 2 then state end) as latest_blood_pressure_2_state, - max(case when rank = 2 then follow_up_facility_name end) as latest_blood_pressure_2_follow_up_facility_name, - max(case when rank = 2 then follow_up_date end) as latest_blood_pressure_2_follow_up_date, - max(case when rank = 2 then follow_up_days end) as latest_blood_pressure_2_follow_up_days, - the_medication_2_updated as latest_blood_pressure_2_medication_updated, - max(case when rank = 2 then prescription_drug_1_name end) as latest_blood_pressure_2_prescription_drug_1_name, - max(case when rank = 2 then prescription_drug_1_dosage end) as latest_blood_pressure_2_prescription_drug_1_dosage, - max(case when rank = 2 then prescription_drug_2_name end) as latest_blood_pressure_2_prescription_drug_2_name, - max(case when rank = 2 then prescription_drug_2_dosage end) as latest_blood_pressure_2_prescription_drug_2_dosage, - max(case when rank = 2 then prescription_drug_3_name end) as latest_blood_pressure_2_prescription_drug_3_name, - max(case when rank = 2 then prescription_drug_3_dosage end) as latest_blood_pressure_2_prescription_drug_3_dosage, - max(case when rank = 2 then prescription_drug_4_name end) as latest_blood_pressure_2_prescription_drug_4_name, - max(case when rank = 2 then prescription_drug_4_dosage end) as latest_blood_pressure_2_prescription_drug_4_dosage, - max(case when rank = 2 then prescription_drug_5_name end) as latest_blood_pressure_2_prescription_drug_5_name, - max(case when rank = 2 then prescription_drug_5_dosage end) as latest_blood_pressure_2_prescription_drug_5_dosage, - max(case when rank = 2 then other_prescription_drugs end) as latest_blood_pressure_2_other_prescription_drugs, - - max(case when rank = 3 then id::text end) as latest_blood_pressure_3_id, - max(case when rank = 3 then recorded_at end) as latest_blood_pressure_3_recorded_at, - max(case when rank = 3 then systolic end) as latest_blood_pressure_3_systolic, - max(case when rank = 3 then diastolic end) as latest_blood_pressure_3_diastolic, - max(case when rank = 3 then facility_name end) as latest_blood_pressure_3_facility_name, - max(case when rank = 3 then facility_type end) as latest_blood_pressure_3_facility_type, - max(case when rank = 3 then district end) as latest_blood_pressure_3_district, - max(case when rank = 3 then state end) as latest_blood_pressure_3_state, - max(case when rank = 3 then follow_up_facility_name end) as latest_blood_pressure_3_follow_up_facility_name, - max(case when rank = 3 then follow_up_date end) as latest_blood_pressure_3_follow_up_date, - max(case when rank = 3 then follow_up_days end) as latest_blood_pressure_3_follow_up_days, - the_medication_3_updated as latest_blood_pressure_3_medication_updated, - max(case when rank = 3 then prescription_drug_1_name end) as latest_blood_pressure_3_prescription_drug_1_name, - max(case when rank = 3 then prescription_drug_1_dosage end) as latest_blood_pressure_3_prescription_drug_1_dosage, - max(case when rank = 3 then prescription_drug_2_name end) as latest_blood_pressure_3_prescription_drug_2_name, - max(case when rank = 3 then prescription_drug_2_dosage end) as latest_blood_pressure_3_prescription_drug_2_dosage, - max(case when rank = 3 then prescription_drug_3_name end) as latest_blood_pressure_3_prescription_drug_3_name, - max(case when rank = 3 then prescription_drug_3_dosage end) as latest_blood_pressure_3_prescription_drug_3_dosage, - max(case when rank = 3 then prescription_drug_4_name end) as latest_blood_pressure_3_prescription_drug_4_name, - max(case when rank = 3 then prescription_drug_4_dosage end) as latest_blood_pressure_3_prescription_drug_4_dosage, - max(case when rank = 3 then prescription_drug_5_name end) as latest_blood_pressure_3_prescription_drug_5_name, - max(case when rank = 3 then prescription_drug_5_dosage end) as latest_blood_pressure_3_prescription_drug_5_dosage, - max(case when rank = 3 then other_prescription_drugs end) as latest_blood_pressure_3_other_prescription_drugs - - from filtered_ranked_blood_pressures - group by - patient_id, - the_medication_1_updated, - the_medication_2_updated, - the_medication_3_updated -), ranked_blood_sugars as ( - select - bs.id, + follow_up_facility.name AS follow_up_facility_name, + a.scheduled_date AS follow_up_date, + (GREATEST((0)::double precision, date_part('day'::text, ((a.scheduled_date)::timestamp without time zone - date_trunc('day'::text, a.device_created_at)))))::integer AS follow_up_days, + bp_drugs.blood_pressure_drugs[1][1] AS prescription_drug_1_name, + bp_drugs.blood_pressure_drugs[1][2] AS prescription_drug_1_dosage, + bp_drugs.blood_pressure_drugs[2][1] AS prescription_drug_2_name, + bp_drugs.blood_pressure_drugs[2][2] AS prescription_drug_2_dosage, + bp_drugs.blood_pressure_drugs[3][1] AS prescription_drug_3_name, + bp_drugs.blood_pressure_drugs[3][2] AS prescription_drug_3_dosage, + bp_drugs.blood_pressure_drugs[4][1] AS prescription_drug_4_name, + bp_drugs.blood_pressure_drugs[4][2] AS prescription_drug_4_dosage, + bp_drugs.blood_pressure_drugs[5][1] AS prescription_drug_5_name, + bp_drugs.blood_pressure_drugs[5][2] AS prescription_drug_5_dosage, + ( SELECT string_agg(value.value, ', '::text) AS string_agg + FROM unnest(bp_drugs.drug_strings[6:]) value(value)) AS other_prescription_drugs, + ( SELECT string_agg(value.value, ', '::text) AS string_agg + FROM unnest(bp_drugs.drug_strings) value(value)) AS all_prescription_drugs, + rank() OVER (PARTITION BY bp.patient_id ORDER BY bp.recorded_at DESC, bp.id) AS rank + FROM ((((public.blood_pressures bp + LEFT JOIN public.facilities f ON ((bp.facility_id = f.id))) + LEFT JOIN ranked_prescription_drugs bp_drugs ON ((bp.id = bp_drugs.bp_id))) + LEFT JOIN blood_pressure_follow_up a ON ((a.bp_id = bp.id))) + LEFT JOIN public.facilities follow_up_facility ON ((follow_up_facility.id = a.facility_id))) + WHERE ((bp.deleted_at IS NULL) AND (a.deleted_at IS NULL)) + ), latest_blood_pressures AS ( + SELECT latest_blood_pressure_1.patient_id, + latest_blood_pressure_1.id AS latest_blood_pressure_1_id, + latest_blood_pressure_1.recorded_at AS latest_blood_pressure_1_recorded_at, + latest_blood_pressure_1.systolic AS latest_blood_pressure_1_systolic, + latest_blood_pressure_1.diastolic AS latest_blood_pressure_1_diastolic, + latest_blood_pressure_1.facility_name AS latest_blood_pressure_1_facility_name, + latest_blood_pressure_1.facility_type AS latest_blood_pressure_1_facility_type, + latest_blood_pressure_1.district AS latest_blood_pressure_1_district, + latest_blood_pressure_1.state AS latest_blood_pressure_1_state, + latest_blood_pressure_1.follow_up_facility_name AS latest_blood_pressure_1_follow_up_facility_name, + latest_blood_pressure_1.follow_up_date AS latest_blood_pressure_1_follow_up_date, + latest_blood_pressure_1.follow_up_days AS latest_blood_pressure_1_follow_up_days, + (latest_blood_pressure_1.all_prescription_drugs <> latest_blood_pressure_2.all_prescription_drugs) AS latest_blood_pressure_1_medication_updated, + latest_blood_pressure_1.prescription_drug_1_name AS latest_blood_pressure_1_prescription_drug_1_name, + latest_blood_pressure_1.prescription_drug_1_dosage AS latest_blood_pressure_1_prescription_drug_1_dosage, + latest_blood_pressure_1.prescription_drug_2_name AS latest_blood_pressure_1_prescription_drug_2_name, + latest_blood_pressure_1.prescription_drug_2_dosage AS latest_blood_pressure_1_prescription_drug_2_dosage, + latest_blood_pressure_1.prescription_drug_3_name AS latest_blood_pressure_1_prescription_drug_3_name, + latest_blood_pressure_1.prescription_drug_3_dosage AS latest_blood_pressure_1_prescription_drug_3_dosage, + latest_blood_pressure_1.prescription_drug_4_name AS latest_blood_pressure_1_prescription_drug_4_name, + latest_blood_pressure_1.prescription_drug_4_dosage AS latest_blood_pressure_1_prescription_drug_4_dosage, + latest_blood_pressure_1.prescription_drug_5_name AS latest_blood_pressure_1_prescription_drug_5_name, + latest_blood_pressure_1.prescription_drug_5_dosage AS latest_blood_pressure_1_prescription_drug_5_dosage, + latest_blood_pressure_1.other_prescription_drugs AS latest_blood_pressure_1_other_prescription_drugs, + latest_blood_pressure_2.id AS latest_blood_pressure_2_id, + latest_blood_pressure_2.recorded_at AS latest_blood_pressure_2_recorded_at, + latest_blood_pressure_2.systolic AS latest_blood_pressure_2_systolic, + latest_blood_pressure_2.diastolic AS latest_blood_pressure_2_diastolic, + latest_blood_pressure_2.facility_name AS latest_blood_pressure_2_facility_name, + latest_blood_pressure_2.facility_type AS latest_blood_pressure_2_facility_type, + latest_blood_pressure_2.district AS latest_blood_pressure_2_district, + latest_blood_pressure_2.state AS latest_blood_pressure_2_state, + latest_blood_pressure_2.follow_up_facility_name AS latest_blood_pressure_2_follow_up_facility_name, + latest_blood_pressure_2.follow_up_date AS latest_blood_pressure_2_follow_up_date, + latest_blood_pressure_2.follow_up_days AS latest_blood_pressure_2_follow_up_days, + (latest_blood_pressure_2.all_prescription_drugs <> latest_blood_pressure_3.all_prescription_drugs) AS latest_blood_pressure_2_medication_updated, + latest_blood_pressure_2.prescription_drug_1_name AS latest_blood_pressure_2_prescription_drug_1_name, + latest_blood_pressure_2.prescription_drug_1_dosage AS latest_blood_pressure_2_prescription_drug_1_dosage, + latest_blood_pressure_2.prescription_drug_2_name AS latest_blood_pressure_2_prescription_drug_2_name, + latest_blood_pressure_2.prescription_drug_2_dosage AS latest_blood_pressure_2_prescription_drug_2_dosage, + latest_blood_pressure_2.prescription_drug_3_name AS latest_blood_pressure_2_prescription_drug_3_name, + latest_blood_pressure_2.prescription_drug_3_dosage AS latest_blood_pressure_2_prescription_drug_3_dosage, + latest_blood_pressure_2.prescription_drug_4_name AS latest_blood_pressure_2_prescription_drug_4_name, + latest_blood_pressure_2.prescription_drug_4_dosage AS latest_blood_pressure_2_prescription_drug_4_dosage, + latest_blood_pressure_2.prescription_drug_5_name AS latest_blood_pressure_2_prescription_drug_5_name, + latest_blood_pressure_2.prescription_drug_5_dosage AS latest_blood_pressure_2_prescription_drug_5_dosage, + latest_blood_pressure_2.other_prescription_drugs AS latest_blood_pressure_2_other_prescription_drugs, + latest_blood_pressure_3.id AS latest_blood_pressure_3_id, + latest_blood_pressure_3.recorded_at AS latest_blood_pressure_3_recorded_at, + latest_blood_pressure_3.systolic AS latest_blood_pressure_3_systolic, + latest_blood_pressure_3.diastolic AS latest_blood_pressure_3_diastolic, + latest_blood_pressure_3.facility_name AS latest_blood_pressure_3_facility_name, + latest_blood_pressure_3.facility_type AS latest_blood_pressure_3_facility_type, + latest_blood_pressure_3.district AS latest_blood_pressure_3_district, + latest_blood_pressure_3.state AS latest_blood_pressure_3_state, + latest_blood_pressure_3.follow_up_facility_name AS latest_blood_pressure_3_follow_up_facility_name, + latest_blood_pressure_3.follow_up_date AS latest_blood_pressure_3_follow_up_date, + latest_blood_pressure_3.follow_up_days AS latest_blood_pressure_3_follow_up_days, + (latest_blood_pressure_3.all_prescription_drugs <> latest_blood_pressure_4.all_prescription_drugs) AS latest_blood_pressure_3_medication_updated, + latest_blood_pressure_3.prescription_drug_1_name AS latest_blood_pressure_3_prescription_drug_1_name, + latest_blood_pressure_3.prescription_drug_1_dosage AS latest_blood_pressure_3_prescription_drug_1_dosage, + latest_blood_pressure_3.prescription_drug_2_name AS latest_blood_pressure_3_prescription_drug_2_name, + latest_blood_pressure_3.prescription_drug_2_dosage AS latest_blood_pressure_3_prescription_drug_2_dosage, + latest_blood_pressure_3.prescription_drug_3_name AS latest_blood_pressure_3_prescription_drug_3_name, + latest_blood_pressure_3.prescription_drug_3_dosage AS latest_blood_pressure_3_prescription_drug_3_dosage, + latest_blood_pressure_3.prescription_drug_4_name AS latest_blood_pressure_3_prescription_drug_4_name, + latest_blood_pressure_3.prescription_drug_4_dosage AS latest_blood_pressure_3_prescription_drug_4_dosage, + latest_blood_pressure_3.prescription_drug_5_name AS latest_blood_pressure_3_prescription_drug_5_name, + latest_blood_pressure_3.prescription_drug_5_dosage AS latest_blood_pressure_3_prescription_drug_5_dosage, + latest_blood_pressure_3.other_prescription_drugs AS latest_blood_pressure_3_other_prescription_drugs + FROM (((ranked_blood_pressures latest_blood_pressure_1 + LEFT JOIN ranked_blood_pressures latest_blood_pressure_2 ON (((latest_blood_pressure_2.patient_id = latest_blood_pressure_1.patient_id) AND (latest_blood_pressure_2.rank = 2)))) + LEFT JOIN ranked_blood_pressures latest_blood_pressure_3 ON (((latest_blood_pressure_3.patient_id = latest_blood_pressure_1.patient_id) AND (latest_blood_pressure_3.rank = 3)))) + LEFT JOIN ranked_blood_pressures latest_blood_pressure_4 ON (((latest_blood_pressure_4.patient_id = latest_blood_pressure_1.patient_id) AND (latest_blood_pressure_4.rank = 4)))) + WHERE (latest_blood_pressure_1.rank = 1) + ), ranked_blood_sugars AS ( + SELECT bs.id, bs.patient_id, - bs.recorded_at as recorded_at, + bs.recorded_at, bs.blood_sugar_type, bs.blood_sugar_value, - f.name as facility_name, + f.name AS facility_name, f.facility_type, f.district, f.state, - follow_up_facility.name as follow_up_facility_name, - a.scheduled_date as follow_up_date, - greatest(0, date_part('day', a.scheduled_date - date_trunc('day',a.device_created_at)))::int as follow_up_days, - rank() over (partition by bs.patient_id order by bs.recorded_at desc, bs.id) rank - from blood_sugars bs - left outer join facilities f on bs.facility_id = f.id - left outer join blood_sugar_follow_up a on a.bs_id = bs.id - left outer join facilities follow_up_facility on follow_up_facility.id = a.facility_id - where bs.deleted_at is null and a.deleted_at is null -), filtered_ranked_blood_sugars as ( - select - rbs.id, - rbs.patient_id, - rbs.recorded_at, - rbs.blood_sugar_type, - rbs.blood_sugar_value, - rbs.facility_name, - rbs.facility_type, - rbs.district, - rbs.state, - rbs.follow_up_facility_name, - rbs.follow_up_date, - rbs.follow_up_days, - rbs.rank - from ranked_blood_sugars rbs - where rank <= 3 -), latest_blood_sugars as ( - select - patient_id, - MAX(CASE WHEN rank = 1 THEN id::text END) AS latest_blood_sugar_1_id, - MAX(CASE WHEN rank = 1 THEN recorded_at END) AS latest_blood_sugar_1_recorded_at, - MAX(CASE WHEN rank = 1 THEN blood_sugar_type END) AS latest_blood_sugar_1_blood_sugar_type, - MAX(CASE WHEN rank = 1 THEN blood_sugar_value END) AS latest_blood_sugar_1_blood_sugar_value, - MAX(CASE WHEN rank = 1 THEN facility_name END) AS latest_blood_sugar_1_facility_name, - MAX(CASE WHEN rank = 1 THEN facility_type END) AS latest_blood_sugar_1_facility_type, - MAX(CASE WHEN rank = 1 THEN district END) AS latest_blood_sugar_1_district, - MAX(CASE WHEN rank = 1 THEN state END) AS latest_blood_sugar_1_state, - MAX(CASE WHEN rank = 1 THEN follow_up_facility_name END) AS latest_blood_sugar_1_follow_up_facility_name, - MAX(CASE WHEN rank = 1 THEN follow_up_date END) AS latest_blood_sugar_1_follow_up_date, - MAX(CASE WHEN rank = 1 THEN follow_up_days END) AS latest_blood_sugar_1_follow_up_days, - MAX(CASE WHEN rank = 2 THEN id::text END) AS latest_blood_sugar_2_id, - MAX(CASE WHEN rank = 2 THEN recorded_at END) AS latest_blood_sugar_2_recorded_at, - MAX(CASE WHEN rank = 2 THEN blood_sugar_type END) AS latest_blood_sugar_2_blood_sugar_type, - MAX(CASE WHEN rank = 2 THEN blood_sugar_value END) AS latest_blood_sugar_2_blood_sugar_value, - MAX(CASE WHEN rank = 2 THEN facility_name END) AS latest_blood_sugar_2_facility_name, - MAX(CASE WHEN rank = 2 THEN facility_type END) AS latest_blood_sugar_2_facility_type, - MAX(CASE WHEN rank = 2 THEN district END) AS latest_blood_sugar_2_district, - MAX(CASE WHEN rank = 2 THEN state END) AS latest_blood_sugar_2_state, - MAX(CASE WHEN rank = 2 THEN follow_up_facility_name END) AS latest_blood_sugar_2_follow_up_facility_name, - MAX(CASE WHEN rank = 2 THEN follow_up_date END) AS latest_blood_sugar_2_follow_up_date, - MAX(CASE WHEN rank = 2 THEN follow_up_days END) AS latest_blood_sugar_2_follow_up_days, - MAX(CASE WHEN rank = 3 THEN id::text END) AS latest_blood_sugar_3_id, - MAX(CASE WHEN rank = 3 THEN recorded_at END) AS latest_blood_sugar_3_recorded_at, - MAX(CASE WHEN rank = 3 THEN blood_sugar_type END) AS latest_blood_sugar_3_blood_sugar_type, - MAX(CASE WHEN rank = 3 THEN blood_sugar_value END) AS latest_blood_sugar_3_blood_sugar_value, - MAX(CASE WHEN rank = 3 THEN facility_name END) AS latest_blood_sugar_3_facility_name, - MAX(CASE WHEN rank = 3 THEN facility_type END) AS latest_blood_sugar_3_facility_type, - MAX(CASE WHEN rank = 3 THEN district END) AS latest_blood_sugar_3_district, - MAX(CASE WHEN rank = 3 THEN state END) AS latest_blood_sugar_3_state, - MAX(CASE WHEN rank = 3 THEN follow_up_facility_name END) AS latest_blood_sugar_3_follow_up_facility_name, - MAX(CASE WHEN rank = 3 THEN follow_up_date END) AS latest_blood_sugar_3_follow_up_date, - MAX(CASE WHEN rank = 3 THEN follow_up_days END) AS latest_blood_sugar_3_follow_up_days - from filtered_ranked_blood_sugars - group by patient_id - order by patient_id -), next_scheduled_appointment as ( - select distinct on (patient_id) - appointments.id, + follow_up_facility.name AS follow_up_facility_name, + a.scheduled_date AS follow_up_date, + (GREATEST((0)::double precision, date_part('day'::text, ((a.scheduled_date)::timestamp without time zone - date_trunc('day'::text, a.device_created_at)))))::integer AS follow_up_days, + rank() OVER (PARTITION BY bs.patient_id ORDER BY bs.recorded_at DESC, bs.id) AS rank + FROM (((public.blood_sugars bs + LEFT JOIN public.facilities f ON ((bs.facility_id = f.id))) + LEFT JOIN blood_sugar_follow_up a ON ((a.bs_id = bs.id))) + LEFT JOIN public.facilities follow_up_facility ON ((follow_up_facility.id = a.facility_id))) + WHERE ((bs.deleted_at IS NULL) AND (a.deleted_at IS NULL)) + ), latest_blood_sugars AS ( + SELECT latest_blood_sugar_1.patient_id, + latest_blood_sugar_1.id AS latest_blood_sugar_1_id, + latest_blood_sugar_1.recorded_at AS latest_blood_sugar_1_recorded_at, + latest_blood_sugar_1.blood_sugar_type AS latest_blood_sugar_1_blood_sugar_type, + latest_blood_sugar_1.blood_sugar_value AS latest_blood_sugar_1_blood_sugar_value, + latest_blood_sugar_1.facility_name AS latest_blood_sugar_1_facility_name, + latest_blood_sugar_1.facility_type AS latest_blood_sugar_1_facility_type, + latest_blood_sugar_1.district AS latest_blood_sugar_1_district, + latest_blood_sugar_1.state AS latest_blood_sugar_1_state, + latest_blood_sugar_1.follow_up_facility_name AS latest_blood_sugar_1_follow_up_facility_name, + latest_blood_sugar_1.follow_up_date AS latest_blood_sugar_1_follow_up_date, + latest_blood_sugar_1.follow_up_days AS latest_blood_sugar_1_follow_up_days, + latest_blood_sugar_2.id AS latest_blood_sugar_2_id, + latest_blood_sugar_2.recorded_at AS latest_blood_sugar_2_recorded_at, + latest_blood_sugar_2.blood_sugar_type AS latest_blood_sugar_2_blood_sugar_type, + latest_blood_sugar_2.blood_sugar_value AS latest_blood_sugar_2_blood_sugar_value, + latest_blood_sugar_2.facility_name AS latest_blood_sugar_2_facility_name, + latest_blood_sugar_2.facility_type AS latest_blood_sugar_2_facility_type, + latest_blood_sugar_2.district AS latest_blood_sugar_2_district, + latest_blood_sugar_2.state AS latest_blood_sugar_2_state, + latest_blood_sugar_2.follow_up_facility_name AS latest_blood_sugar_2_follow_up_facility_name, + latest_blood_sugar_2.follow_up_date AS latest_blood_sugar_2_follow_up_date, + latest_blood_sugar_2.follow_up_days AS latest_blood_sugar_2_follow_up_days, + latest_blood_sugar_3.id AS latest_blood_sugar_3_id, + latest_blood_sugar_3.recorded_at AS latest_blood_sugar_3_recorded_at, + latest_blood_sugar_3.blood_sugar_type AS latest_blood_sugar_3_blood_sugar_type, + latest_blood_sugar_3.blood_sugar_value AS latest_blood_sugar_3_blood_sugar_value, + latest_blood_sugar_3.facility_name AS latest_blood_sugar_3_facility_name, + latest_blood_sugar_3.facility_type AS latest_blood_sugar_3_facility_type, + latest_blood_sugar_3.district AS latest_blood_sugar_3_district, + latest_blood_sugar_3.state AS latest_blood_sugar_3_state, + latest_blood_sugar_3.follow_up_facility_name AS latest_blood_sugar_3_follow_up_facility_name, + latest_blood_sugar_3.follow_up_date AS latest_blood_sugar_3_follow_up_date, + latest_blood_sugar_3.follow_up_days AS latest_blood_sugar_3_follow_up_days + FROM ((ranked_blood_sugars latest_blood_sugar_1 + LEFT JOIN ranked_blood_sugars latest_blood_sugar_2 ON (((latest_blood_sugar_2.patient_id = latest_blood_sugar_1.patient_id) AND (latest_blood_sugar_2.rank = 2)))) + LEFT JOIN ranked_blood_sugars latest_blood_sugar_3 ON (((latest_blood_sugar_3.patient_id = latest_blood_sugar_1.patient_id) AND (latest_blood_sugar_3.rank = 3)))) + WHERE (latest_blood_sugar_1.rank = 1) + ), next_scheduled_appointment AS ( + SELECT DISTINCT ON (appointments.patient_id) appointments.id, + appointments.patient_id, + appointments.facility_id, appointments.scheduled_date, appointments.status, + appointments.cancel_reason, + appointments.device_created_at, + appointments.device_updated_at, + appointments.created_at, + appointments.updated_at, appointments.remind_on, - appointments.patient_id, - f.id as appointment_facility_id, - f.name as appointment_facility_name, - f.facility_type appointment_facility_type, - f.district as appointment_district, - f.state as appointment_state - from appointments - left outer join facilities f on f.id = appointments.facility_id and appointments.status = 'scheduled' - where appointments.deleted_at is null - order by patient_id, device_created_at desc -) - -select distinct on (p.id) - p.id, + appointments.agreed_to_visit, + appointments.deleted_at, + appointments.appointment_type, + appointments.user_id, + appointments.creation_facility_id, + f.id AS appointment_facility_id, + f.name AS appointment_facility_name, + f.facility_type AS appointment_facility_type, + f.district AS appointment_district, + f.state AS appointment_state + FROM (public.appointments + LEFT JOIN public.facilities f ON (((f.id = appointments.facility_id) AND ((appointments.status)::text = 'scheduled'::text)))) + WHERE (appointments.deleted_at IS NULL) + ORDER BY appointments.patient_id, appointments.device_created_at DESC + ) + SELECT DISTINCT ON (p.id) p.id, p.recorded_at, - p.full_name as full_name, - latest_bp_passport.id as latest_bp_passport_id, - latest_bp_passport.identifier as latest_bp_passport_identifier, - extract(year from coalesce( - age(p.date_of_birth), - make_interval(years => p.age) + age(p.age_updated_at) - )) as current_age, + p.full_name, + latest_bp_passport.id AS latest_bp_passport_id, + latest_bp_passport.identifier AS latest_bp_passport_identifier, + EXTRACT(year FROM COALESCE(age((p.date_of_birth)::timestamp with time zone), (make_interval(years => p.age) + age(p.age_updated_at)))) AS current_age, p.gender, p.status, - latest_phone_number.number as latest_phone_number, + latest_phone_number.number AS latest_phone_number, addresses.village_or_colony, addresses.street_address, addresses.district, addresses.zone, addresses.state, - - assigned_facility.name as assigned_facility_name, - assigned_facility.facility_type as assigned_facility_type, - assigned_facility.state as assigned_facility_state, - assigned_facility.district as assigned_facility_district, - - registration_facility.name as registration_facility_name, - registration_facility.facility_type as registration_facility_type, - registration_facility.state as registration_facility_state, - registration_facility.district as registration_facility_district, - + assigned_facility.name AS assigned_facility_name, + assigned_facility.facility_type AS assigned_facility_type, + assigned_facility.state AS assigned_facility_state, + assigned_facility.district AS assigned_facility_district, + registration_facility.name AS registration_facility_name, + registration_facility.facility_type AS registration_facility_type, + registration_facility.state AS registration_facility_state, + registration_facility.district AS registration_facility_district, mh.hypertension, mh.diabetes, - - greatest(0, date_part('day', NOW() - next_scheduled_appointment.scheduled_date)) as days_overdue, - next_scheduled_appointment.id as next_scheduled_appointment_id, - next_scheduled_appointment.scheduled_date as next_scheduled_appointment_scheduled_date, - next_scheduled_appointment.status as next_scheduled_appointment_status, - next_scheduled_appointment.remind_on as next_scheduled_appointment_remind_on, - next_scheduled_appointment.appointment_facility_id as next_scheduled_appointment_facility_id, - next_scheduled_appointment.appointment_facility_name as next_scheduled_appointment_facility_name, - next_scheduled_appointment.appointment_facility_type as next_scheduled_appointment_facility_type, - next_scheduled_appointment.appointment_district as next_scheduled_appointment_district, - next_scheduled_appointment.appointment_state as next_scheduled_appointment_state, - + GREATEST((0)::double precision, date_part('day'::text, (now() - (next_scheduled_appointment.scheduled_date)::timestamp with time zone))) AS days_overdue, + next_scheduled_appointment.id AS next_scheduled_appointment_id, + next_scheduled_appointment.scheduled_date AS next_scheduled_appointment_scheduled_date, + next_scheduled_appointment.status AS next_scheduled_appointment_status, + next_scheduled_appointment.remind_on AS next_scheduled_appointment_remind_on, + next_scheduled_appointment.appointment_facility_id AS next_scheduled_appointment_facility_id, + next_scheduled_appointment.appointment_facility_name AS next_scheduled_appointment_facility_name, + next_scheduled_appointment.appointment_facility_type AS next_scheduled_appointment_facility_type, + next_scheduled_appointment.appointment_district AS next_scheduled_appointment_district, + next_scheduled_appointment.appointment_state AS next_scheduled_appointment_state, latest_blood_pressures.latest_blood_pressure_1_id, latest_blood_pressures.latest_blood_pressure_1_recorded_at, latest_blood_pressures.latest_blood_pressure_1_systolic, @@ -377,7 +357,6 @@ select distinct on (p.id) latest_blood_pressures.latest_blood_pressure_1_prescription_drug_5_name, latest_blood_pressures.latest_blood_pressure_1_prescription_drug_5_dosage, latest_blood_pressures.latest_blood_pressure_1_other_prescription_drugs, - latest_blood_pressures.latest_blood_pressure_2_id, latest_blood_pressures.latest_blood_pressure_2_recorded_at, latest_blood_pressures.latest_blood_pressure_2_systolic, @@ -401,7 +380,6 @@ select distinct on (p.id) latest_blood_pressures.latest_blood_pressure_2_prescription_drug_5_name, latest_blood_pressures.latest_blood_pressure_2_prescription_drug_5_dosage, latest_blood_pressures.latest_blood_pressure_2_other_prescription_drugs, - latest_blood_pressures.latest_blood_pressure_3_id, latest_blood_pressures.latest_blood_pressure_3_recorded_at, latest_blood_pressures.latest_blood_pressure_3_systolic, @@ -425,7 +403,6 @@ select distinct on (p.id) latest_blood_pressures.latest_blood_pressure_3_prescription_drug_5_name, latest_blood_pressures.latest_blood_pressure_3_prescription_drug_5_dosage, latest_blood_pressures.latest_blood_pressure_3_other_prescription_drugs, - latest_blood_sugars.latest_blood_sugar_1_id, latest_blood_sugars.latest_blood_sugar_1_recorded_at, latest_blood_sugars.latest_blood_sugar_1_blood_sugar_type, @@ -437,7 +414,6 @@ select distinct on (p.id) latest_blood_sugars.latest_blood_sugar_1_follow_up_facility_name, latest_blood_sugars.latest_blood_sugar_1_follow_up_date, latest_blood_sugars.latest_blood_sugar_1_follow_up_days, - latest_blood_sugars.latest_blood_sugar_2_id, latest_blood_sugars.latest_blood_sugar_2_recorded_at, latest_blood_sugars.latest_blood_sugar_2_blood_sugar_type, @@ -449,7 +425,6 @@ select distinct on (p.id) latest_blood_sugars.latest_blood_sugar_2_follow_up_facility_name, latest_blood_sugars.latest_blood_sugar_2_follow_up_date, latest_blood_sugars.latest_blood_sugar_2_follow_up_days, - latest_blood_sugars.latest_blood_sugar_3_id, latest_blood_sugars.latest_blood_sugar_3_recorded_at, latest_blood_sugars.latest_blood_sugar_3_blood_sugar_type, @@ -461,14 +436,14 @@ select distinct on (p.id) latest_blood_sugars.latest_blood_sugar_3_follow_up_facility_name, latest_blood_sugars.latest_blood_sugar_3_follow_up_date, latest_blood_sugars.latest_blood_sugar_3_follow_up_days -from patients p -left outer join latest_bp_passport on latest_bp_passport.patient_id = p.id -left outer join latest_phone_number on latest_phone_number.patient_id = p.id -left outer join addresses on addresses.id = p.address_id -left outer join facilities assigned_facility on assigned_facility.id = p.assigned_facility_id -left outer join facilities registration_facility on registration_facility.id = p.registration_facility_id -left outer join latest_medical_history mh on mh.patient_id = p.id -left outer join latest_blood_pressures on latest_blood_pressures.patient_id = p.id -left outer join latest_blood_sugars on latest_blood_sugars.patient_id = p.id -left outer join next_scheduled_appointment on next_scheduled_appointment.patient_id = p.id -where p.deleted_at is null + FROM (((((((((public.patients p + LEFT JOIN latest_bp_passport ON ((latest_bp_passport.patient_id = p.id))) + LEFT JOIN latest_phone_number ON ((latest_phone_number.patient_id = p.id))) + LEFT JOIN public.addresses ON ((addresses.id = p.address_id))) + LEFT JOIN public.facilities assigned_facility ON ((assigned_facility.id = p.assigned_facility_id))) + LEFT JOIN public.facilities registration_facility ON ((registration_facility.id = p.registration_facility_id))) + LEFT JOIN latest_medical_history mh ON ((mh.patient_id = p.id))) + LEFT JOIN latest_blood_pressures ON ((latest_blood_pressures.patient_id = p.id))) + LEFT JOIN latest_blood_sugars ON ((latest_blood_sugars.patient_id = p.id))) + LEFT JOIN next_scheduled_appointment ON ((next_scheduled_appointment.patient_id = p.id))) + WHERE (p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL); \ No newline at end of file diff --git a/db/views/reporting_facility_appointment_scheduled_days_v05.sql b/db/views/reporting_facility_appointment_scheduled_days_v05.sql new file mode 100644 index 0000000000..8d1fb7d79f --- /dev/null +++ b/db/views/reporting_facility_appointment_scheduled_days_v05.sql @@ -0,0 +1,51 @@ +WITH latest_medical_histories AS ( + SELECT DISTINCT ON (patient_id) mh.* + FROM medical_histories mh + WHERE mh.deleted_at IS NULL + ORDER BY patient_id, mh.device_created_at DESC +), + latest_appointments_per_patient_per_month AS ( + SELECT DISTINCT ON (patient_id, month_date) a.*, + lmh.hypertension, + lmh.diabetes, + to_char(a.device_created_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')), 'YYYY-MM-01')::date month_date + FROM appointments a + INNER JOIN patients p ON p.id = a.patient_id + INNER JOIN latest_medical_histories lmh ON lmh.patient_id = a.patient_id + WHERE a.scheduled_date >= date_trunc('day', a.device_created_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE'))) + AND a.device_created_at >= date_trunc('month', (now() AT TIME ZONE 'UTC') - INTERVAL '6 months') + AND date_trunc('month', a.device_created_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE'))) + > date_trunc('month', p.recorded_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE'))) + AND p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL + AND a.deleted_at IS NULL + AND (lmh.hypertension = 'yes' OR lmh.diabetes = 'yes') + ORDER BY a.patient_id, month_date, a.device_created_at desc +), + scheduled_days_distribution AS ( + SELECT month_date, + width_bucket( + extract('days' FROM (scheduled_date - date_trunc('day', device_created_at AT TIME ZONE 'UTC' AT TIME ZONE (SELECT current_setting('TIMEZONE')))))::integer, + array[0, 15, 32, 63] + ) bucket, + COUNT(*) number_of_appointments, + hypertension, + diabetes, + creation_facility_id facility_id + FROM latest_appointments_per_patient_per_month + GROUP BY bucket, creation_facility_id, month_date, hypertension, diabetes) + +SELECT facility_id, + month_date, + (SUM(number_of_appointments) FILTER (WHERE bucket = 1 AND hypertension = 'yes'))::integer AS htn_appts_scheduled_0_to_14_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 2 AND hypertension = 'yes'))::integer AS htn_appts_scheduled_15_to_31_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 3 AND hypertension = 'yes'))::integer AS htn_appts_scheduled_32_to_62_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 4 AND hypertension = 'yes'))::integer AS htn_appts_scheduled_more_than_62_days, + (SUM(number_of_appointments) FILTER (WHERE hypertension = 'yes'))::integer htn_total_appts_scheduled, + + (SUM(number_of_appointments) FILTER (WHERE bucket = 1 AND diabetes = 'yes'))::integer AS diabetes_appts_scheduled_0_to_14_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 2 AND diabetes = 'yes'))::integer AS diabetes_appts_scheduled_15_to_31_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 3 AND diabetes = 'yes'))::integer AS diabetes_appts_scheduled_32_to_62_days, + (SUM(number_of_appointments) FILTER (WHERE bucket = 4 AND diabetes = 'yes'))::integer AS diabetes_appts_scheduled_more_than_62_days, + (SUM(number_of_appointments) FILTER (WHERE diabetes = 'yes'))::integer diabetes_total_appts_scheduled +FROM scheduled_days_distribution +GROUP BY facility_id, month_date \ No newline at end of file diff --git a/db/views/reporting_facility_daily_follow_ups_and_registrations_v03.sql b/db/views/reporting_facility_daily_follow_ups_and_registrations_v03.sql new file mode 100644 index 0000000000..ac38a39355 --- /dev/null +++ b/db/views/reporting_facility_daily_follow_ups_and_registrations_v03.sql @@ -0,0 +1,206 @@ +WITH follow_up_blood_pressures AS ( +SELECT DISTINCT ON (((EXTRACT(doy FROM ((bp.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), bp.facility_id, p.id) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + bp.id AS visit_id, + 'BloodPressure'::text AS visit_type, + bp.facility_id, + bp.user_id, + bp.recorded_at AS visited_at, + (EXTRACT(doy FROM ((bp.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year + FROM (public.patients p + JOIN public.blood_pressures bp ON (((p.id = bp.patient_id) AND (date_trunc('day'::text, ((bp.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.diagnosed_confirmed_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) + WHERE ((p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) AND (bp.recorded_at > (CURRENT_TIMESTAMP - '30 days'::interval))) +), follow_up_blood_sugars AS ( +SELECT DISTINCT ON (((EXTRACT(doy FROM ((bs.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), bs.facility_id, p.id) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + bs.id AS visit_id, + 'BloodSugar'::text AS visit_type, + bs.facility_id, + bs.user_id, + bs.recorded_at AS visited_at, + (EXTRACT(doy FROM ((bs.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year + FROM (public.patients p + JOIN public.blood_sugars bs ON (((p.id = bs.patient_id) AND (date_trunc('day'::text, ((bs.recorded_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.diagnosed_confirmed_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) + WHERE ((p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) AND (bs.recorded_at > (CURRENT_TIMESTAMP - '30 days'::interval))) +), follow_up_prescription_drugs AS ( +SELECT DISTINCT ON (((EXTRACT(doy FROM ((pd.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), pd.facility_id, p.id) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + pd.id AS visit_id, + 'PrescriptionDrug'::text AS visit_type, + pd.facility_id, + pd.user_id, + pd.device_created_at AS visited_at, + (EXTRACT(doy FROM ((pd.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year + FROM (public.patients p + JOIN public.prescription_drugs pd ON (((p.id = pd.patient_id) AND (date_trunc('day'::text, ((pd.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.diagnosed_confirmed_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) + WHERE ((p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) AND (pd.device_created_at > (CURRENT_TIMESTAMP - '30 days'::interval))) +), follow_up_appointments AS ( + SELECT DISTINCT ON (((EXTRACT(doy FROM ((app.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), app.creation_facility_id, p.id) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + app.id AS visit_id, + 'Appointment'::text AS visit_type, + app.creation_facility_id AS facility_id, + app.user_id, + app.device_created_at AS visited_at, + (EXTRACT(doy FROM ((app.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year + FROM (public.patients p + JOIN public.appointments app ON (((p.id = app.patient_id) AND (date_trunc('day'::text, ((app.device_created_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) > date_trunc('day'::text, ((p.diagnosed_confirmed_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))))) + WHERE ((p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) AND (app.device_created_at > (CURRENT_TIMESTAMP - '30 days'::interval))) + ), registered_patients AS ( + SELECT DISTINCT ON (((EXTRACT(doy FROM ((p.diagnosed_confirmed_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer), p.registration_facility_id, p.id) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + p.id AS visit_id, + 'Registration'::text AS visit_type, + p.registration_facility_id AS facility_id, + p.registration_user_id AS user_id, + p.diagnosed_confirmed_at AS visited_at, + (EXTRACT(doy FROM ((p.diagnosed_confirmed_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year + FROM public.patients p + WHERE ((p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) AND (p.diagnosed_confirmed_at > (CURRENT_TIMESTAMP - '30 days'::interval))) + ), all_follow_ups AS ( + SELECT follow_up_blood_pressures.patient_id, + follow_up_blood_pressures.patient_gender, + follow_up_blood_pressures.visit_id, + follow_up_blood_pressures.visit_type, + follow_up_blood_pressures.facility_id, + follow_up_blood_pressures.user_id, + follow_up_blood_pressures.visited_at, + follow_up_blood_pressures.day_of_year + FROM follow_up_blood_pressures + UNION + SELECT follow_up_blood_sugars.patient_id, + follow_up_blood_sugars.patient_gender, + follow_up_blood_sugars.visit_id, + follow_up_blood_sugars.visit_type, + follow_up_blood_sugars.facility_id, + follow_up_blood_sugars.user_id, + follow_up_blood_sugars.visited_at, + follow_up_blood_sugars.day_of_year + FROM follow_up_blood_sugars + UNION + SELECT follow_up_prescription_drugs.patient_id, + follow_up_prescription_drugs.patient_gender, + follow_up_prescription_drugs.visit_id, + follow_up_prescription_drugs.visit_type, + follow_up_prescription_drugs.facility_id, + follow_up_prescription_drugs.user_id, + follow_up_prescription_drugs.visited_at, + follow_up_prescription_drugs.day_of_year + FROM follow_up_prescription_drugs + UNION + SELECT follow_up_appointments.patient_id, + follow_up_appointments.patient_gender, + follow_up_appointments.visit_id, + follow_up_appointments.visit_type, + follow_up_appointments.facility_id, + follow_up_appointments.user_id, + follow_up_appointments.visited_at, + follow_up_appointments.day_of_year + FROM follow_up_appointments + ), all_follow_ups_with_medical_histories AS ( + SELECT DISTINCT ON (all_follow_ups.day_of_year, all_follow_ups.facility_id, all_follow_ups.patient_id) all_follow_ups.patient_id, + all_follow_ups.patient_gender, + all_follow_ups.facility_id, + mh.diabetes, + mh.hypertension, + all_follow_ups.user_id, + all_follow_ups.visit_id, + all_follow_ups.visit_type, + all_follow_ups.visited_at, + all_follow_ups.day_of_year + FROM (all_follow_ups + JOIN public.medical_histories mh ON ((all_follow_ups.patient_id = mh.patient_id))) + ), registered_patients_with_medical_histories AS ( + SELECT DISTINCT ON (registered_patients.day_of_year, registered_patients.facility_id, registered_patients.patient_id) registered_patients.patient_id, + registered_patients.patient_gender, + registered_patients.facility_id, + mh.diabetes, + mh.hypertension, + registered_patients.user_id, + registered_patients.visit_id, + registered_patients.visit_type, + registered_patients.visited_at, + registered_patients.day_of_year + FROM (registered_patients + JOIN public.medical_histories mh ON ((registered_patients.patient_id = mh.patient_id))) + ), daily_registered_patients AS ( + SELECT DISTINCT ON (registered_patients_with_medical_histories.facility_id, (date(((registered_patients_with_medical_histories.visited_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))) registered_patients_with_medical_histories.facility_id, + date(((registered_patients_with_medical_histories.visited_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) AS visit_date, + registered_patients_with_medical_histories.day_of_year, + count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) OR (registered_patients_with_medical_histories.diabetes = 'yes'::text))) AS daily_registrations_htn_or_dm, + count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text))) AS daily_registrations_htn_and_dm, + count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'female'::public.gender_enum))) AS daily_registrations_htn_and_dm_female, + count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'male'::public.gender_enum))) AS daily_registrations_htn_and_dm_male, + count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'transgender'::public.gender_enum))) AS daily_registrations_htn_and_dm_transgender, + (count(*) FILTER (WHERE (registered_patients_with_medical_histories.hypertension = 'yes'::text)) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text)))) AS daily_registrations_htn_only, + (count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'female'::public.gender_enum))) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'female'::public.gender_enum)))) AS daily_registrations_htn_only_female, + (count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'male'::public.gender_enum))) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'male'::public.gender_enum)))) AS daily_registrations_htn_only_male, + (count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'transgender'::public.gender_enum))) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'transgender'::public.gender_enum)))) AS daily_registrations_htn_only_transgender, + (count(*) FILTER (WHERE (registered_patients_with_medical_histories.diabetes = 'yes'::text)) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text)))) AS daily_registrations_dm_only, + (count(*) FILTER (WHERE ((registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'female'::public.gender_enum))) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'female'::public.gender_enum)))) AS daily_registrations_dm_only_female, + (count(*) FILTER (WHERE ((registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'male'::public.gender_enum))) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'male'::public.gender_enum)))) AS daily_registrations_dm_only_male, + (count(*) FILTER (WHERE ((registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'transgender'::public.gender_enum))) - count(*) FILTER (WHERE ((registered_patients_with_medical_histories.hypertension = 'yes'::text) AND (registered_patients_with_medical_histories.diabetes = 'yes'::text) AND (registered_patients_with_medical_histories.patient_gender = 'transgender'::public.gender_enum)))) AS daily_registrations_dm_only_transgender + FROM registered_patients_with_medical_histories + GROUP BY registered_patients_with_medical_histories.facility_id, (date(((registered_patients_with_medical_histories.visited_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting)))), registered_patients_with_medical_histories.day_of_year + ), daily_follow_ups AS ( + SELECT DISTINCT ON (all_follow_ups_with_medical_histories.facility_id, (date(((all_follow_ups_with_medical_histories.visited_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))) all_follow_ups_with_medical_histories.facility_id, + date(((all_follow_ups_with_medical_histories.visited_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))) AS visit_date, + all_follow_ups_with_medical_histories.day_of_year, + count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) OR (all_follow_ups_with_medical_histories.diabetes = 'yes'::text))) AS daily_follow_ups_htn_or_dm, + count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text))) AS daily_follow_ups_htn_and_dm, + count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'female'::public.gender_enum))) AS daily_follow_ups_htn_and_dm_female, + count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'male'::public.gender_enum))) AS daily_follow_ups_htn_and_dm_male, + count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'transgender'::public.gender_enum))) AS daily_follow_ups_htn_and_dm_transgender, + (count(*) FILTER (WHERE (all_follow_ups_with_medical_histories.hypertension = 'yes'::text)) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text)))) AS daily_follow_ups_htn_only, + (count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'female'::public.gender_enum))) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'female'::public.gender_enum)))) AS daily_follow_ups_htn_only_female, + (count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'male'::public.gender_enum))) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'male'::public.gender_enum)))) AS daily_follow_ups_htn_only_male, + (count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'transgender'::public.gender_enum))) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'transgender'::public.gender_enum)))) AS daily_follow_ups_htn_only_transgender, + (count(*) FILTER (WHERE (all_follow_ups_with_medical_histories.diabetes = 'yes'::text)) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text)))) AS daily_follow_ups_dm_only, + (count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'female'::public.gender_enum))) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'female'::public.gender_enum)))) AS daily_follow_ups_dm_only_female, + (count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'male'::public.gender_enum))) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'male'::public.gender_enum)))) AS daily_follow_ups_dm_only_male, + (count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'transgender'::public.gender_enum))) - count(*) FILTER (WHERE ((all_follow_ups_with_medical_histories.hypertension = 'yes'::text) AND (all_follow_ups_with_medical_histories.diabetes = 'yes'::text) AND (all_follow_ups_with_medical_histories.patient_gender = 'transgender'::public.gender_enum)))) AS daily_follow_ups_dm_only_transgender + FROM all_follow_ups_with_medical_histories + GROUP BY all_follow_ups_with_medical_histories.facility_id, (date(((all_follow_ups_with_medical_histories.visited_at AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting)))), all_follow_ups_with_medical_histories.day_of_year + ), last_30_days AS ( + SELECT (generate_series((CURRENT_TIMESTAMP - '30 days'::interval), CURRENT_TIMESTAMP, '1 day'::interval))::date AS date + ) +SELECT rf.facility_region_slug, + rf.facility_id, + rf.facility_region_id, + rf.block_region_id, + rf.district_region_id, + rf.state_region_id, + last_30_days.date AS visit_date, + (EXTRACT(doy FROM ((last_30_days.date AT TIME ZONE 'UTC'::text) AT TIME ZONE ( SELECT current_setting('TIMEZONE'::text) AS current_setting))))::integer AS day_of_year, + COALESCE(daily_registered_patients.daily_registrations_htn_or_dm, (0)::bigint) AS daily_registrations_htn_or_dm, + COALESCE(daily_registered_patients.daily_registrations_htn_only, (0)::bigint) AS daily_registrations_htn_only, + COALESCE(daily_registered_patients.daily_registrations_htn_only_male, (0)::bigint) AS daily_registrations_htn_only_male, + COALESCE(daily_registered_patients.daily_registrations_htn_only_female, (0)::bigint) AS daily_registrations_htn_only_female, + COALESCE(daily_registered_patients.daily_registrations_htn_only_transgender, (0)::bigint) AS daily_registrations_htn_only_transgender, + COALESCE(daily_registered_patients.daily_registrations_dm_only, (0)::bigint) AS daily_registrations_dm_only, + COALESCE(daily_registered_patients.daily_registrations_dm_only_male, (0)::bigint) AS daily_registrations_dm_only_male, + COALESCE(daily_registered_patients.daily_registrations_dm_only_female, (0)::bigint) AS daily_registrations_dm_only_female, + COALESCE(daily_registered_patients.daily_registrations_dm_only_transgender, (0)::bigint) AS daily_registrations_dm_only_transgender, + COALESCE(daily_registered_patients.daily_registrations_htn_and_dm, (0)::bigint) AS daily_registrations_htn_and_dm, + COALESCE(daily_registered_patients.daily_registrations_htn_and_dm_male, (0)::bigint) AS daily_registrations_htn_and_dm_male, + COALESCE(daily_registered_patients.daily_registrations_htn_and_dm_female, (0)::bigint) AS daily_registrations_htn_and_dm_female, + COALESCE(daily_registered_patients.daily_registrations_htn_and_dm_transgender, (0)::bigint) AS daily_registrations_htn_and_dm_transgender, + COALESCE(daily_follow_ups.daily_follow_ups_htn_or_dm, (0)::bigint) AS daily_follow_ups_htn_or_dm, + COALESCE(daily_follow_ups.daily_follow_ups_htn_only, (0)::bigint) AS daily_follow_ups_htn_only, + COALESCE(daily_follow_ups.daily_follow_ups_htn_only_female, (0)::bigint) AS daily_follow_ups_htn_only_female, + COALESCE(daily_follow_ups.daily_follow_ups_htn_only_male, (0)::bigint) AS daily_follow_ups_htn_only_male, + COALESCE(daily_follow_ups.daily_follow_ups_htn_only_transgender, (0)::bigint) AS daily_follow_ups_htn_only_transgender, + COALESCE(daily_follow_ups.daily_follow_ups_dm_only, (0)::bigint) AS daily_follow_ups_dm_only, + COALESCE(daily_follow_ups.daily_follow_ups_dm_only_female, (0)::bigint) AS daily_follow_ups_dm_only_female, + COALESCE(daily_follow_ups.daily_follow_ups_dm_only_male, (0)::bigint) AS daily_follow_ups_dm_only_male, + COALESCE(daily_follow_ups.daily_follow_ups_dm_only_transgender, (0)::bigint) AS daily_follow_ups_dm_only_transgender, + COALESCE(daily_follow_ups.daily_follow_ups_htn_and_dm, (0)::bigint) AS daily_follow_ups_htn_and_dm, + COALESCE(daily_follow_ups.daily_follow_ups_htn_and_dm_male, (0)::bigint) AS daily_follow_ups_htn_and_dm_male, + COALESCE(daily_follow_ups.daily_follow_ups_htn_and_dm_female, (0)::bigint) AS daily_follow_ups_htn_and_dm_female, + COALESCE(daily_follow_ups.daily_follow_ups_htn_and_dm_transgender, (0)::bigint) AS daily_follow_ups_htn_and_dm_transgender + FROM (((public.reporting_facilities rf + JOIN last_30_days ON (true)) + LEFT JOIN daily_registered_patients ON (((daily_registered_patients.visit_date = last_30_days.date) AND (daily_registered_patients.facility_id = rf.facility_id)))) + LEFT JOIN daily_follow_ups ON (((daily_follow_ups.visit_date = last_30_days.date) AND (daily_follow_ups.facility_id = rf.facility_id)))) + ORDER BY last_30_days.date DESC; + \ No newline at end of file diff --git a/db/views/reporting_overdue_calls_v02.sql b/db/views/reporting_overdue_calls_v02.sql new file mode 100644 index 0000000000..ff3f45a861 --- /dev/null +++ b/db/views/reporting_overdue_calls_v02.sql @@ -0,0 +1,30 @@ +SELECT DISTINCT ON (a.patient_id, cal.month_date) cal.month_date, + cal.month_string, + cal.month, + cal.quarter, + cal.year, + timezone('UTC'::text, timezone('UTC'::text, cr.device_created_at)) AS call_result_created_at, + cr.id AS call_result_id, + cr.user_id, + a.id AS appointment_id, + a.facility_id AS appointment_facility_id, + a.patient_id, + appointment_facility.facility_size AS appointment_facility_size, + appointment_facility.facility_type AS appointment_facility_type, + appointment_facility.facility_region_slug AS appointment_facility_slug, + appointment_facility.facility_region_id AS appointment_facility_region_id, + appointment_facility.block_slug AS appointment_block_slug, + appointment_facility.block_region_id AS appointment_block_region_id, + appointment_facility.district_slug AS appointment_district_slug, + appointment_facility.district_region_id AS appointment_district_region_id, + appointment_facility.state_slug AS appointment_state_slug, + appointment_facility.state_region_id AS appointment_state_region_id, + appointment_facility.organization_slug AS appointment_organization_slug, + appointment_facility.organization_region_id AS appointment_organization_region_id +FROM (((public.call_results cr + JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, cr.device_created_at)), 'YYYY-MM'::text) = to_char((cal.month_date)::timestamp with time zone, 'YYYY-MM'::text)))) + JOIN public.appointments a ON (((cr.appointment_id = a.id) AND (a.deleted_at IS NULL)))) + JOIN public.patients p ON (p.id = a.patient_id AND ((p.deleted_at IS NULL) AND (p.diagnosed_confirmed_at IS NOT NULL))) + JOIN public.reporting_facilities appointment_facility ON ((a.facility_id = appointment_facility.facility_id))) +WHERE (cr.deleted_at IS NULL) +ORDER BY a.patient_id, cal.month_date, cr.device_created_at DESC; \ No newline at end of file diff --git a/db/views/reporting_patient_blood_pressures_v04.sql b/db/views/reporting_patient_blood_pressures_v04.sql new file mode 100644 index 0000000000..6545da30fb --- /dev/null +++ b/db/views/reporting_patient_blood_pressures_v04.sql @@ -0,0 +1,24 @@ +SELECT DISTINCT ON (bp.patient_id, cal.month_date) cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string, + timezone('UTC'::text, timezone('UTC'::text, bp.recorded_at)) AS blood_pressure_recorded_at, + bp.id AS blood_pressure_id, + bp.patient_id, + bp.systolic, + bp.diastolic, + bp.facility_id AS blood_pressure_facility_id, + timezone('UTC'::text, timezone('UTC'::text, p.diagnosed_confirmed_at)) AS patient_registered_at, + p.assigned_facility_id AS patient_assigned_facility_id, + p.registration_facility_id AS patient_registration_facility_id, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at)))) * (12)::double precision) + (cal.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))) AS months_since_registration, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at)))) * (4)::double precision) + (cal.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))) AS quarters_since_registration, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)))) * (12)::double precision) + (cal.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at))))) AS months_since_bp, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)))) * (4)::double precision) + (cal.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at))))) AS quarters_since_bp +FROM ((public.blood_pressures bp +LEFT JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)), 'YYYY-MM'::text) <= to_char((cal.month_date)::timestamp with time zone, 'YYYY-MM'::text)))) +JOIN public.patients p ON (((bp.patient_id = p.id) AND (p.deleted_at IS NULL) AND (p.diagnosed_confirmed_at IS NOT NULL)))) +WHERE (bp.deleted_at IS NULL) +ORDER BY bp.patient_id, cal.month_date, bp.recorded_at DESC; \ No newline at end of file diff --git a/db/views/reporting_patient_blood_sugars_v02.sql b/db/views/reporting_patient_blood_sugars_v02.sql new file mode 100644 index 0000000000..4e1e386976 --- /dev/null +++ b/db/views/reporting_patient_blood_sugars_v02.sql @@ -0,0 +1,49 @@ +-- Only most recent BS per patient per month. BSs are ordered appropriately below. +SELECT DISTINCT ON (bs.patient_id, cal.month_date) cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string, + timezone('UTC'::text, timezone('UTC'::text, bs.recorded_at)) AS blood_sugar_recorded_at, + bs.id AS blood_sugar_id, + bs.patient_id, + bs.blood_sugar_type, + bs.blood_sugar_value, + bs.facility_id AS blood_sugar_facility_id, + CASE + WHEN (((bs.blood_sugar_type)::text = 'random'::text) OR ((bs.blood_sugar_type)::text = 'post_prandial'::text)) THEN + CASE + WHEN (bs.blood_sugar_value < 200.0) THEN 'bs_below_200'::text + WHEN ((bs.blood_sugar_value >= 200.0) AND (bs.blood_sugar_value < 300.0)) THEN 'bs_200_to_300'::text + WHEN (bs.blood_sugar_value >= 300.0) THEN 'bs_over_300'::text + ELSE NULL::text + END + WHEN ((bs.blood_sugar_type)::text = 'fasting'::text) THEN + CASE + WHEN (bs.blood_sugar_value < 126.0) THEN 'bs_below_200'::text + WHEN ((bs.blood_sugar_value >= 126.0) AND (bs.blood_sugar_value < 200.0)) THEN 'bs_200_to_300'::text + WHEN (bs.blood_sugar_value >= 200.0) THEN 'bs_over_300'::text + ELSE NULL::text + END + WHEN ((bs.blood_sugar_type)::text = 'hba1c'::text) THEN + CASE + WHEN (bs.blood_sugar_value < 7.0) THEN 'bs_below_200'::text + WHEN ((bs.blood_sugar_value >= 7.0) AND (bs.blood_sugar_value < 9.0)) THEN 'bs_200_to_300'::text + WHEN (bs.blood_sugar_value >= 9.0) THEN 'bs_over_300'::text + ELSE NULL::text + END + ELSE NULL::text + END AS blood_sugar_risk_state, + timezone('UTC'::text, timezone('UTC'::text, p.diagnosed_confirmed_at)) AS patient_registered_at, + p.assigned_facility_id AS patient_assigned_facility_id, + p.registration_facility_id AS patient_registration_facility_id, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at)))) * (12)::double precision) + (cal.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))) AS months_since_registration, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at)))) * (4)::double precision) + (cal.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))) AS quarters_since_registration, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)))) * (12)::double precision) + (cal.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at))))) AS months_since_bs, + (((cal.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)))) * (4)::double precision) + (cal.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at))))) AS quarters_since_bs + FROM ((public.blood_sugars bs + LEFT JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)), 'YYYY-MM'::text) <= to_char((cal.month_date)::timestamp with time zone, 'YYYY-MM'::text)))) + JOIN public.patients p ON (((bs.patient_id = p.id) AND (p.deleted_at IS NULL) AND (p.diagnosed_confirmed_at IS NOT NULL)))) + WHERE (bs.deleted_at IS NULL) + ORDER BY bs.patient_id, cal.month_date, bs.recorded_at DESC; \ No newline at end of file diff --git a/db/views/reporting_patient_follow_ups_v05.sql b/db/views/reporting_patient_follow_ups_v05.sql new file mode 100644 index 0000000000..28f19f206d --- /dev/null +++ b/db/views/reporting_patient_follow_ups_v05.sql @@ -0,0 +1,108 @@ +WITH follow_up_blood_pressures AS ( + SELECT DISTINCT ON (p.id, bp.facility_id, bp.user_id, (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)), 'YYYY-MM'::text))) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + bp.id AS visit_id, + 'BloodPressure'::text AS visit_type, + bp.facility_id, + bp.user_id, + bp.recorded_at AS visited_at, + to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at)), 'YYYY-MM'::text) AS month_string + FROM (public.patients p + JOIN public.blood_pressures bp ON (((p.id = bp.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bp.recorded_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))))) + WHERE (p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) +), follow_up_blood_sugars AS ( + SELECT DISTINCT ON (p.id, bs.facility_id, bs.user_id, (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)), 'YYYY-MM'::text))) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + bs.id AS visit_id, + 'BloodSugar'::text AS visit_type, + bs.facility_id, + bs.user_id, + bs.recorded_at AS visited_at, + to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at)), 'YYYY-MM'::text) AS month_string + FROM (public.patients p + JOIN public.blood_sugars bs ON (((p.id = bs.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, bs.recorded_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))))) + WHERE (p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) +), follow_up_prescription_drugs AS ( + SELECT DISTINCT ON (p.id, pd.facility_id, pd.user_id, (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, pd.device_created_at)), 'YYYY-MM'::text))) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + pd.id AS visit_id, + 'PrescriptionDrug'::text AS visit_type, + pd.facility_id, + pd.user_id, + pd.device_created_at AS visited_at, + to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, pd.device_created_at)), 'YYYY-MM'::text) AS month_string + FROM (public.patients p + JOIN public.prescription_drugs pd ON (((p.id = pd.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, pd.device_created_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))))) + WHERE (p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) +), follow_up_appointments AS ( + SELECT DISTINCT ON (p.id, app.creation_facility_id, app.user_id, (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, app.device_created_at)), 'YYYY-MM'::text))) p.id AS patient_id, + (p.gender)::public.gender_enum AS patient_gender, + app.id AS visit_id, + 'Appointment'::text AS visit_type, + app.creation_facility_id AS facility_id, + app.user_id, + app.device_created_at AS visited_at, + to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, app.device_created_at)), 'YYYY-MM'::text) AS month_string + FROM (public.patients p + JOIN public.appointments app ON (((p.id = app.patient_id) AND (date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, app.device_created_at))) > date_trunc('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, p.diagnosed_confirmed_at))))))) + WHERE (p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) +), all_follow_ups AS ( + SELECT follow_up_blood_pressures.patient_id, + follow_up_blood_pressures.patient_gender, + follow_up_blood_pressures.visit_id, + follow_up_blood_pressures.visit_type, + follow_up_blood_pressures.facility_id, + follow_up_blood_pressures.user_id, + follow_up_blood_pressures.visited_at, + follow_up_blood_pressures.month_string + FROM follow_up_blood_pressures +UNION + SELECT follow_up_blood_sugars.patient_id, + follow_up_blood_sugars.patient_gender, + follow_up_blood_sugars.visit_id, + follow_up_blood_sugars.visit_type, + follow_up_blood_sugars.facility_id, + follow_up_blood_sugars.user_id, + follow_up_blood_sugars.visited_at, + follow_up_blood_sugars.month_string + FROM follow_up_blood_sugars +UNION + SELECT follow_up_prescription_drugs.patient_id, + follow_up_prescription_drugs.patient_gender, + follow_up_prescription_drugs.visit_id, + follow_up_prescription_drugs.visit_type, + follow_up_prescription_drugs.facility_id, + follow_up_prescription_drugs.user_id, + follow_up_prescription_drugs.visited_at, + follow_up_prescription_drugs.month_string + FROM follow_up_prescription_drugs +UNION + SELECT follow_up_appointments.patient_id, + follow_up_appointments.patient_gender, + follow_up_appointments.visit_id, + follow_up_appointments.visit_type, + follow_up_appointments.facility_id, + follow_up_appointments.user_id, + follow_up_appointments.visited_at, + follow_up_appointments.month_string + FROM follow_up_appointments +) +SELECT DISTINCT ON (cal.month_string, all_follow_ups.facility_id, all_follow_ups.user_id, all_follow_ups.patient_id) all_follow_ups.patient_id, + all_follow_ups.patient_gender, + all_follow_ups.facility_id, + mh.diabetes, + mh.hypertension, + all_follow_ups.user_id, + all_follow_ups.visit_id, + all_follow_ups.visit_type, + all_follow_ups.visited_at, + cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string +FROM ((all_follow_ups + JOIN public.medical_histories mh ON ((all_follow_ups.patient_id = mh.patient_id))) + LEFT JOIN public.reporting_months cal ON ((all_follow_ups.month_string = cal.month_string))) +ORDER BY cal.month_string DESC; \ No newline at end of file diff --git a/db/views/reporting_patient_visits_v04.sql b/db/views/reporting_patient_visits_v04.sql new file mode 100644 index 0000000000..78694a16bb --- /dev/null +++ b/db/views/reporting_patient_visits_v04.sql @@ -0,0 +1,131 @@ +SELECT DISTINCT ON (p.id, p.month_date) p.id AS patient_id, + p.month_date, + p.month, + p.quarter, + p.year, + p.month_string, + p.quarter_string, + p.assigned_facility_id, + p.registration_facility_id, + timezone('UTC'::text, timezone('UTC'::text, p.diagnosed_confirmed_at)) AS patient_recorded_at, + e.id AS encounter_id, + e.facility_id AS encounter_facility_id, + timezone('UTC'::text, timezone('UTC'::text, e.recorded_at)) AS encounter_recorded_at, + pd.id AS prescription_drug_id, + pd.facility_id AS prescription_drug_facility_id, + timezone('UTC'::text, timezone('UTC'::text, pd.recorded_at)) AS prescription_drug_recorded_at, + app.id AS appointment_id, + app.creation_facility_id AS appointment_creation_facility_id, + timezone('UTC'::text, timezone('UTC'::text, app.recorded_at)) AS appointment_recorded_at, + array_remove(ARRAY[ + CASE + WHEN (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, e.recorded_at)), 'YYYY-MM'::text) = p.month_string) THEN e.facility_id + ELSE NULL::uuid + END, + CASE + WHEN (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, pd.recorded_at)), 'YYYY-MM'::text) = p.month_string) THEN pd.facility_id + ELSE NULL::uuid + END, + CASE + WHEN (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, app.recorded_at)), 'YYYY-MM'::text) = p.month_string) THEN app.creation_facility_id + ELSE NULL::uuid + END], NULL::uuid) AS visited_facility_ids, + timezone('UTC'::text, timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at))) AS visited_at, + (((p.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.diagnosed_confirmed_at)))) * (12)::double precision) + (p.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.diagnosed_confirmed_at))))) AS months_since_registration, + (((p.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.diagnosed_confirmed_at)))) * (4)::double precision) + (p.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p.diagnosed_confirmed_at))))) AS quarters_since_registration, + (((p.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at))))) * (12)::double precision) + (p.month - date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at)))))) AS months_since_visit, + (((p.year - date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at))))) * (4)::double precision) + (p.quarter - date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at)))))) AS quarters_since_visit + FROM (((( SELECT p_1.id, + p_1.full_name, + p_1.age, + p_1.gender, + p_1.date_of_birth, + p_1.status, + p_1.created_at, + p_1.updated_at, + p_1.address_id, + p_1.age_updated_at, + p_1.device_created_at, + p_1.device_updated_at, + p_1.test_data, + p_1.registration_facility_id, + p_1.registration_user_id, + p_1.deleted_at, + p_1.contacted_by_counsellor, + p_1.could_not_contact_reason, + p_1.recorded_at, + p_1.reminder_consent, + p_1.deleted_by_user_id, + p_1.deleted_reason, + p_1.assigned_facility_id, + p_1.diagnosed_confirmed_at, + cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string + FROM (public.patients p_1 + LEFT JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p_1.diagnosed_confirmed_at)), 'YYYY-MM'::text) <= cal.month_string)))) p + LEFT JOIN LATERAL ( SELECT timezone('UTC'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), (encounters.encountered_on)::timestamp without time zone)) AS recorded_at, + encounters.id, + encounters.facility_id, + encounters.patient_id, + encounters.encountered_on, + encounters.timezone_offset, + encounters.notes, + encounters.metadata, + encounters.device_created_at, + encounters.device_updated_at, + encounters.deleted_at, + encounters.created_at, + encounters.updated_at + FROM public.encounters + WHERE ((encounters.patient_id = p.id) AND (to_char((encounters.encountered_on)::timestamp with time zone, 'YYYY-MM'::text) <= p.month_string) AND (encounters.deleted_at IS NULL)) + ORDER BY encounters.encountered_on DESC + LIMIT 1) e ON (true)) + LEFT JOIN LATERAL ( SELECT prescription_drugs.device_created_at AS recorded_at, + prescription_drugs.id, + prescription_drugs.name, + prescription_drugs.rxnorm_code, + prescription_drugs.dosage, + prescription_drugs.device_created_at, + prescription_drugs.device_updated_at, + prescription_drugs.created_at, + prescription_drugs.updated_at, + prescription_drugs.patient_id, + prescription_drugs.facility_id, + prescription_drugs.is_protocol_drug, + prescription_drugs.is_deleted, + prescription_drugs.deleted_at, + prescription_drugs.user_id, + prescription_drugs.frequency, + prescription_drugs.duration_in_days, + prescription_drugs.teleconsultation_id + FROM public.prescription_drugs + WHERE ((prescription_drugs.patient_id = p.id) AND (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, prescription_drugs.device_created_at)), 'YYYY-MM'::text) <= p.month_string) AND (prescription_drugs.deleted_at IS NULL)) + ORDER BY prescription_drugs.device_created_at DESC + LIMIT 1) pd ON (true)) + LEFT JOIN LATERAL ( SELECT appointments.device_created_at AS recorded_at, + appointments.id, + appointments.patient_id, + appointments.facility_id, + appointments.scheduled_date, + appointments.status, + appointments.cancel_reason, + appointments.device_created_at, + appointments.device_updated_at, + appointments.created_at, + appointments.updated_at, + appointments.remind_on, + appointments.agreed_to_visit, + appointments.deleted_at, + appointments.appointment_type, + appointments.user_id, + appointments.creation_facility_id + FROM public.appointments + WHERE ((appointments.patient_id = p.id) AND (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, appointments.device_created_at)), 'YYYY-MM'::text) <= p.month_string) AND (appointments.deleted_at IS NULL)) + ORDER BY appointments.device_created_at DESC + LIMIT 1) app ON (true)) + WHERE (p.deleted_at IS NULL AND p.diagnosed_confirmed_at IS NOT NULL) + ORDER BY p.id, p.month_date, (timezone('UTC'::text, timezone('UTC'::text, GREATEST(e.recorded_at, pd.recorded_at, app.recorded_at)))) DESC; \ No newline at end of file diff --git a/db/views/reporting_prescriptions_v04.sql b/db/views/reporting_prescriptions_v04.sql new file mode 100644 index 0000000000..13f203f7c0 --- /dev/null +++ b/db/views/reporting_prescriptions_v04.sql @@ -0,0 +1,55 @@ +SELECT p.id AS patient_id, + p.month_date, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Amlodipine'::text)), (0)::double precision) AS amlodipine, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Telmisartan'::text)), (0)::double precision) AS telmisartan, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Losartan Potassium'::text)), (0)::double precision) AS losartan, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Atenolol'::text)), (0)::double precision) AS atenolol, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Enalapril'::text)), (0)::double precision) AS enalapril, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Chlorthalidone'::text)), (0)::double precision) AS chlorthalidone, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE ((prescriptions.clean_name)::text = 'Hydrochlorothiazide'::text)), (0)::double precision) AS hydrochlorothiazide, + COALESCE(max(prescriptions.clean_dosage) FILTER (WHERE (((prescriptions.clean_name)::text <> ALL (ARRAY[('Amlodipine'::character varying)::text, ('Telmisartan'::character varying)::text, ('Losartan'::character varying)::text, ('Atenolol'::character varying)::text, ('Enalapril'::character varying)::text, ('Chlorthalidone'::character varying)::text, ('Hydrochlorothiazide'::character varying)::text])) AND (prescriptions.medicine_purpose_hypertension = true))), (0)::double precision) AS other_bp_medications +FROM (( SELECT p_1.id, + p_1.full_name, + p_1.age, + p_1.gender, + p_1.date_of_birth, + p_1.status, + p_1.created_at, + p_1.updated_at, + p_1.address_id, + p_1.age_updated_at, + p_1.device_created_at, + p_1.device_updated_at, + p_1.test_data, + p_1.registration_facility_id, + p_1.registration_user_id, + p_1.deleted_at, + p_1.contacted_by_counsellor, + p_1.could_not_contact_reason, + p_1.recorded_at, + p_1.reminder_consent, + p_1.deleted_by_user_id, + p_1.deleted_reason, + p_1.assigned_facility_id, + cal.month_date, + cal.month, + cal.quarter, + cal.year, + cal.month_string, + cal.quarter_string + FROM (public.patients p_1 + LEFT JOIN public.reporting_months cal ON ((to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, p_1.diagnosed_confirmed_at)), 'YYYY-MM'::text) <= cal.month_string))) + WHERE (p_1.deleted_at IS NULL AND p_1.diagnosed_confirmed_at IS NOT NULL)) p + LEFT JOIN LATERAL ( SELECT DISTINCT ON (clean.medicine) actual.name AS actual_name, + actual.dosage AS actual_dosage, + clean.medicine AS clean_name, + clean.dosage AS clean_dosage, + purpose.hypertension AS medicine_purpose_hypertension, + purpose.diabetes AS medicine_purpose_diabetes + FROM (((public.prescription_drugs actual + LEFT JOIN public.raw_to_clean_medicines raw ON (((lower(regexp_replace((raw.raw_name)::text, '\s+'::text, ''::text, 'g'::text)) = lower(regexp_replace((actual.name)::text, '\s+'::text, ''::text, 'g'::text))) AND (lower(regexp_replace((raw.raw_dosage)::text, '\s+'::text, ''::text, 'g'::text)) = lower(regexp_replace((actual.dosage)::text, '\s+'::text, ''::text, 'g'::text)))))) + LEFT JOIN public.clean_medicine_to_dosages clean ON ((clean.rxcui = raw.rxcui))) + LEFT JOIN public.medicine_purposes purpose ON (((clean.medicine)::text = (purpose.name)::text))) + WHERE ((actual.patient_id = p.id) AND (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, actual.device_created_at)), 'YYYY-MM'::text) <= p.month_string) AND (actual.deleted_at IS NULL) AND ((actual.is_deleted = false) OR ((actual.is_deleted = true) AND (to_char(timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('UTC'::text, actual.device_updated_at)), 'YYYY-MM'::text) > p.month_string)))) + ORDER BY clean.medicine, actual.device_created_at DESC) prescriptions ON (true)) + GROUP BY p.id, p.month_date \ No newline at end of file diff --git a/lib/tasks/reporting.rake b/lib/tasks/reporting.rake index 9927343330..d309603708 100644 --- a/lib/tasks/reporting.rake +++ b/lib/tasks/reporting.rake @@ -17,4 +17,37 @@ namespace :reporting do end end end + + desc "Do a refresh of said materialized views" + task :initial_materialized_view_refresh, [:views] => :environment do |_, args| + # Default list + default_views = %w[ + latest_blood_pressures_per_patient_per_months + latest_blood_pressures_per_patient_per_quarters + latest_blood_pressures_per_patients + blood_pressures_per_facility_per_days + materialized_patient_summaries + reporting_patient_blood_pressures + reporting_patient_blood_sugars + reporting_overdue_calls + reporting_patient_visits + reporting_prescriptions + reporting_patient_follow_ups + reporting_facility_appointment_scheduled_days + reporting_facility_states + reporting_facility_daily_follow_ups_and_registrations + reporting_facility_monthly_follow_ups_and_registrations + ].freeze + views_to_refresh = + if args[:views].present? + # Expecting a comma-separated list: "view1,view2" + [args[:views], *args.extras].map(&:strip) + else + default_views + end + views_to_refresh.each do |view_name| + puts "Refreshing materialized view: #{view_name} " + ActiveRecord::Base.connection.exec_query("REFRESH MATERIALIZED VIEW #{view_name}") + end + end end diff --git a/spec/controllers/api/v3/medical_histories_controller_spec.rb b/spec/controllers/api/v3/medical_histories_controller_spec.rb old mode 100644 new mode 100755 index ee916b1441..81bac37315 --- a/spec/controllers/api/v3/medical_histories_controller_spec.rb +++ b/spec/controllers/api/v3/medical_histories_controller_spec.rb @@ -167,6 +167,18 @@ def create_record_list(n, options = {}) expect(history.receiving_treatment_for_diabetes).to eq(expected_values[:receiving_treatment_for_diabetes]) end + it "sets patient's diagnosed_confirmed_at to earliest of diagnosis timestamps when provided" do + early = 8.days.ago + late = 3.days.ago + params[:medical_histories][0][:htn_diagnosed_at] = late.rfc3339 + params[:medical_histories][0][:dm_diagnosed_at] = early.rfc3339 + + post :sync_from_user, params: params + + expect(response.status).to eq 200 + expect(patient.reload.diagnosed_confirmed_at.to_i).to eq(early.to_i) + end + it "leaves values at nil when not provided" do params[:medical_histories][0].delete(:receiving_treatment_for_diabetes) @@ -187,6 +199,14 @@ def create_record_list(n, options = {}) ) expect(patient.reload.medical_history).to eq(nil) end + + it "accepts 'suspected' as a valid hypertension value" do + params[:medical_histories][0][:hypertension] = "suspected" + post :sync_from_user, params: params + + expect(response.status).to eq 200 + expect(patient.reload.medical_history.hypertension).to eq("suspected") + end end end end diff --git a/spec/controllers/api/v3/medical_histories_immutable_dates_spec.rb b/spec/controllers/api/v3/medical_histories_immutable_dates_spec.rb new file mode 100644 index 0000000000..4ad71bcd46 --- /dev/null +++ b/spec/controllers/api/v3/medical_histories_immutable_dates_spec.rb @@ -0,0 +1,142 @@ +require "rails_helper" + +RSpec.describe Api::V3::MedicalHistoriesController, type: :controller do + let(:request_user) { create(:user) } + let(:request_facility_group) { request_user.facility.facility_group } + let(:request_facility) { create(:facility, facility_group: request_facility_group) } + let(:patient) { create(:patient, registration_facility: request_facility) } + + before { set_authentication_headers } + + describe "immutable diagnosis dates validation" do + context "when updating an existing medical history with diagnosis dates" do + let(:existing_medical_history) do + create(:medical_history, + patient: patient, + htn_diagnosed_at: 1.year.ago.round, + dm_diagnosed_at: 6.months.ago.round, + device_updated_at: 10.minutes.ago) + end + + let(:params) do + { + medical_histories: [{ + id: existing_medical_history.id, + patient_id: patient.id, + prior_heart_attack: "no", + prior_stroke: "no", + chronic_kidney_disease: "no", + receiving_treatment_for_hypertension: "yes", + receiving_treatment_for_diabetes: "no", + diabetes: "no", + hypertension: "yes", + diagnosed_with_hypertension: "yes", + smoking: "no", + smokeless_tobacco: "no", + htn_diagnosed_at: 2.years.ago.iso8601, + dm_diagnosed_at: 1.year.ago.iso8601, + created_at: existing_medical_history.created_at.iso8601, + updated_at: Time.current.iso8601 + }] + } + end + + it "returns 200 and silently preserves existing diagnosis dates" do + post :sync_from_user, params: params + + expect(response.status).to eq 200 + parsed_body = JSON.parse(response.body) + expect(parsed_body["errors"]).to be_blank + end + + it "does not update the diagnosis dates" do + original_htn = existing_medical_history.htn_diagnosed_at + original_dm = existing_medical_history.dm_diagnosed_at + + post :sync_from_user, params: params + existing_medical_history.reload + expect(existing_medical_history.htn_diagnosed_at).to eq original_htn + expect(existing_medical_history.dm_diagnosed_at).to eq original_dm + end + end + + context "when updating medical history without changing diagnosis dates" do + let(:existing_medical_history) do + create(:medical_history, + patient: patient, + htn_diagnosed_at: 1.year.ago.round, + dm_diagnosed_at: 6.months.ago.round, + device_updated_at: 10.minutes.ago) + end + + let(:params) do + { + medical_histories: [{ + id: existing_medical_history.id, + patient_id: patient.id, + prior_heart_attack: "yes", + prior_stroke: "no", + chronic_kidney_disease: "no", + receiving_treatment_for_hypertension: "yes", + receiving_treatment_for_diabetes: "no", + diabetes: "no", + hypertension: "yes", + diagnosed_with_hypertension: "yes", + smoking: "no", + smokeless_tobacco: "no", + htn_diagnosed_at: existing_medical_history.htn_diagnosed_at.utc.iso8601, + dm_diagnosed_at: existing_medical_history.dm_diagnosed_at.utc.iso8601, + created_at: existing_medical_history.created_at.utc.iso8601, + updated_at: Time.current.utc.iso8601 + }] + } + end + + it "successfully updates the medical history" do + post :sync_from_user, params: params + + expect(response.status).to eq 200 + parsed_body = JSON.parse(response.body) + expect(parsed_body["errors"]).to be_blank + + existing_medical_history.reload + expect(existing_medical_history.prior_heart_attack).to eq "yes" + end + end + + context "when creating a new medical history with diagnosis dates" do + let(:params) do + { + medical_histories: [{ + id: SecureRandom.uuid, + patient_id: patient.id, + prior_heart_attack: "no", + prior_stroke: "no", + chronic_kidney_disease: "no", + receiving_treatment_for_hypertension: "yes", + receiving_treatment_for_diabetes: "no", + diabetes: "no", + hypertension: "yes", + diagnosed_with_hypertension: "yes", + smoking: "no", + smokeless_tobacco: "no", + htn_diagnosed_at: 1.year.ago.round.iso8601, + dm_diagnosed_at: 6.months.ago.round.iso8601, + created_at: Time.current.iso8601, + updated_at: Time.current.iso8601 + }] + } + end + + it "successfully creates the medical history with diagnosis dates" do + expect { + post :sync_from_user, params: params + }.to change { MedicalHistory.count }.by_at_least(1) + + expect(response.status).to eq 200 + parsed_body = JSON.parse(response.body) + expect(parsed_body["errors"]).to be_blank + end + end + end +end diff --git a/spec/exporters/patients_with_history_exporter_spec.rb b/spec/exporters/patients_with_history_exporter_spec.rb index 98f43d1a28..05d3c4caba 100644 --- a/spec/exporters/patients_with_history_exporter_spec.rb +++ b/spec/exporters/patients_with_history_exporter_spec.rb @@ -206,8 +206,8 @@ registration_facility.facility_type, registration_facility.district, registration_facility.state, - "no", "yes", + "no", bp_1_follow_up.days_overdue, I18n.l(bp_1.recorded_at.to_date), quarter_string(bp_1.recorded_at.to_date), @@ -333,7 +333,6 @@ before do allow(Rails.application.config.country).to receive(:[]).with(:patient_line_list_show_zone).and_return(true) - patient.medical_history.update!(hypertension: "no", diabetes: "yes") MaterializedPatientSummary.refresh end diff --git a/spec/factories/medical_histories.rb b/spec/factories/medical_histories.rb index d4f90b17f9..af593b3174 100644 --- a/spec/factories/medical_histories.rb +++ b/spec/factories/medical_histories.rb @@ -42,6 +42,10 @@ hypertension { MedicalHistory::MEDICAL_HISTORY_ANSWERS[:unknown] } end + trait :hypertension_suspected do + hypertension { "suspected" } + end + trait :diabetes_yes do diabetes { MedicalHistory::MEDICAL_HISTORY_ANSWERS[:yes] } end @@ -54,6 +58,10 @@ diabetes { MedicalHistory::MEDICAL_HISTORY_ANSWERS[:unknown] } end + trait :diabetes_suspected do + diabetes { "suspected" } + end + trait :prior_risk_history do prior_heart_attack_boolean { true } prior_stroke_boolean { true } diff --git a/spec/factories/patients.rb b/spec/factories/patients.rb index 0b976db406..48d8494f1e 100644 --- a/spec/factories/patients.rb +++ b/spec/factories/patients.rb @@ -15,6 +15,7 @@ device_created_at { Time.current } device_updated_at { Time.current } recorded_at { device_created_at } + diagnosed_confirmed_at { recorded_at } association :address, strategy: :build phone_numbers do [association(:patient_phone_number, strategy: :build, patient: instance)] @@ -57,6 +58,7 @@ trait :without_medical_history do medical_history { nil } + diagnosed_confirmed_at { nil } end trait :denied do diff --git a/spec/models/blood_pressures_per_facility_per_day_spec.rb b/spec/models/blood_pressures_per_facility_per_day_spec.rb index a9b4025ddb..f7d4b1615e 100644 --- a/spec/models/blood_pressures_per_facility_per_day_spec.rb +++ b/spec/models/blood_pressures_per_facility_per_day_spec.rb @@ -52,5 +52,13 @@ it "doesn't have a row for facility_without_bp" do expect(described_class.all.pluck(:facility_id)).not_to include(facility_without_bp.id) end + + context "screening" do + it "doesn't include blood pressures of screened patients" do + screened_patient = create(:patient, registration_facility: facility_with_bp, diagnosed_confirmed_at: nil) + create(:blood_pressure, facility: facility_with_bp, recorded_at: days.first, patient: screened_patient) + expect(described_class.where(year: 1.day.ago.year, day: 1.day.ago.yday).first.bp_count).to eq(2) + end + end end end diff --git a/spec/models/latest_blood_pressures_per_patient_per_month_spec.rb b/spec/models/latest_blood_pressures_per_patient_per_month_spec.rb index d2d6690f11..4949c3328b 100644 --- a/spec/models/latest_blood_pressures_per_patient_per_month_spec.rb +++ b/spec/models/latest_blood_pressures_per_patient_per_month_spec.rb @@ -135,7 +135,7 @@ def create_blood_pressures it "stores and updates medical_history_hypertension" do patient_1 = create(:patient) patient_2 = create(:patient, :without_hypertension) - patient_3 = create(:patient, :without_medical_history) + patient_3 = create(:patient, :without_medical_history, diagnosed_confirmed_at: Date.today) create(:blood_pressure, patient: patient_1) create(:blood_pressure, patient: patient_2) @@ -151,4 +151,13 @@ def create_blood_pressures expect(bp_per_month_3.medical_history_hypertension).to be_nil end end + + context "screening" do + it "should not include blood pressures of patients under screening" do + patient = create(:patient, diagnosed_confirmed_at: nil, medical_history: build(:medical_history, :hypertension_suspected, :diabetes_suspected)) + create(:blood_pressure, patient: patient) + LatestBloodPressuresPerPatientPerMonth.refresh + expect(LatestBloodPressuresPerPatientPerMonth.where(patient_id: patient.id).count).to eq(0) + end + end end diff --git a/spec/models/materialized_patient_summary_spec.rb b/spec/models/materialized_patient_summary_spec.rb index 8879e824ac..2054948b0a 100644 --- a/spec/models/materialized_patient_summary_spec.rb +++ b/spec/models/materialized_patient_summary_spec.rb @@ -330,4 +330,12 @@ def refresh_view end end end + + context "screening" do + it "does not include screened patients" do + patient.update!(diagnosed_confirmed_at: nil) + refresh_view + expect(described_class.count).to eq(0) + end + end end diff --git a/spec/models/medical_history_spec.rb b/spec/models/medical_history_spec.rb index b6328fa055..6784721042 100644 --- a/spec/models/medical_history_spec.rb +++ b/spec/models/medical_history_spec.rb @@ -46,4 +46,122 @@ expect(patient.medical_history.indicates_hypertension_risk?).to eq(false) end end + + describe "#backfill_diagnosed_dates" do + let(:patient) { create(:patient, :without_medical_history, recorded_at: 5.days.ago.change(usec: 0), diagnosed_confirmed_at: nil) } + + it "backfills htn_diagnosed_at from patient.recorded_at when hypertension is yes and dates are nil (old APK flow)" do + mh = create(:medical_history, patient: patient, hypertension: "yes", htn_diagnosed_at: nil, dm_diagnosed_at: nil) + expect(mh.htn_diagnosed_at.to_i).to eq(patient.recorded_at.to_i) + end + + it "backfills dm_diagnosed_at from patient.recorded_at when diabetes is yes and dates are nil (old APK flow)" do + mh = create(:medical_history, patient: patient, diabetes: "yes", htn_diagnosed_at: nil, dm_diagnosed_at: nil) + expect(mh.dm_diagnosed_at.to_i).to eq(patient.recorded_at.to_i) + end + + it "backfills htn_diagnosed_at from patient.recorded_at when hypertension is no and dates are nil (old APK flow)" do + mh = create(:medical_history, patient: patient, hypertension: "no", htn_diagnosed_at: nil, dm_diagnosed_at: nil) + expect(mh.htn_diagnosed_at.to_i).to eq(patient.recorded_at.to_i) + end + + it "backfills dm_diagnosed_at from patient.recorded_at when diabetes is no and dates are nil (old APK flow)" do + mh = create(:medical_history, patient: patient, diabetes: "no", htn_diagnosed_at: nil, dm_diagnosed_at: nil) + expect(mh.dm_diagnosed_at.to_i).to eq(patient.recorded_at.to_i) + end + + it "does not backfill for suspected statuses" do + mh = create(:medical_history, patient: patient, hypertension: "suspected", htn_diagnosed_at: nil) + expect(mh.htn_diagnosed_at).to be_nil + end + end + + describe "#update_patient_diagnosed_confirmed_at" do + let(:patient) { create(:patient, :without_medical_history, recorded_at: 5.days.ago.change(usec: 0), diagnosed_confirmed_at: nil) } + + it "sets patient.diagnosed_confirmed_at to htn_diagnosed_at when only HTN date present (other suspected)" do + htn_time = 3.days.ago.change(usec: 0) + create(:medical_history, patient: patient, hypertension: "yes", diabetes: "suspected", htn_diagnosed_at: htn_time, dm_diagnosed_at: nil) + expect(patient.reload.diagnosed_confirmed_at.to_i).to eq(htn_time.to_i) + end + + it "sets patient.diagnosed_confirmed_at to dm_diagnosed_at when only DM date present (other suspected)" do + dm_time = 5.days.ago.change(usec: 0) + create(:medical_history, patient: patient, diabetes: "no", hypertension: "suspected", htn_diagnosed_at: nil, dm_diagnosed_at: dm_time) + expect(patient.reload.diagnosed_confirmed_at.to_i).to eq(dm_time.to_i) + end + + it "sets patient.diagnosed_confirmed_at to earliest date when both dates present" do + htn = 4.days.ago.change(usec: 0) + dm = 7.days.ago.change(usec: 0) + create(:medical_history, patient: patient, hypertension: "yes", diabetes: "no", htn_diagnosed_at: htn, dm_diagnosed_at: dm) + expect(patient.reload.diagnosed_confirmed_at.to_i).to eq(dm.to_i) + end + + it "does not set diagnosed_confirmed_at when both conditions are suspected" do + create(:medical_history, patient: patient, hypertension: "suspected", diabetes: "suspected", + htn_diagnosed_at: 4.days.ago.change(usec: 0), dm_diagnosed_at: 3.days.ago.change(usec: 0)) + expect(patient.reload.diagnosed_confirmed_at).to be_nil + end + + it "does not overwrite existing patient.diagnosed_confirmed_at" do + earlier = 2.days.ago.change(usec: 0) + patient.update!(diagnosed_confirmed_at: earlier) + create(:medical_history, patient: patient, hypertension: "yes", htn_diagnosed_at: 5.days.ago.change(usec: 0)) + expect(patient.reload.diagnosed_confirmed_at.to_i).to eq(earlier.to_i) + end + end + + describe "immutable diagnosis dates and transitions" do + let(:patient) { create(:patient) } + + it "silently preserves htn_diagnosed_at once set" do + original_date = 5.days.ago.change(usec: 0) + history = create(:medical_history, patient: patient, htn_diagnosed_at: original_date, hypertension: "yes") + result = history.update(htn_diagnosed_at: 1.day.ago.change(usec: 0)) + expect(result).to be true + expect(history.errors).to be_blank + expect(history.reload.htn_diagnosed_at.to_i).to eq(original_date.to_i) + end + + it "silently preserves dm_diagnosed_at once set" do + original_date = 5.days.ago.change(usec: 0) + history = create(:medical_history, patient: patient, dm_diagnosed_at: original_date, diabetes: "yes") + result = history.update(dm_diagnosed_at: 1.day.ago.change(usec: 0)) + expect(result).to be true + expect(history.errors).to be_blank + expect(history.reload.dm_diagnosed_at.to_i).to eq(original_date.to_i) + end + + it "allows setting the other diagnosis when one is already set (confirmed + suspected)" do + history = create(:medical_history, + patient: patient, + hypertension: "yes", + diabetes: "suspected", + htn_diagnosed_at: 5.days.ago.change(usec: 0), + dm_diagnosed_at: nil) + expect(history.update(dm_diagnosed_at: 2.days.ago.change(usec: 0))).to be true + end + + it "allows setting either when both are nil" do + history = create(:medical_history, patient: patient, hypertension: "suspected", diabetes: "suspected", htn_diagnosed_at: nil, dm_diagnosed_at: nil) + expect(history.update(htn_diagnosed_at: 3.days.ago.change(usec: 0))).to be true + end + + it "does not allow flipping confirmed yes/no when dates are already present (preserves old dates on save)" do + old_htn = 10.days.ago.change(usec: 0) + old_dm = 9.days.ago.change(usec: 0) + mh = create(:medical_history, patient: patient, + hypertension: "yes", diabetes: "no", + htn_diagnosed_at: old_htn, dm_diagnosed_at: old_dm) + + mh.assign_attributes(hypertension: "no", diabetes: "yes", htn_diagnosed_at: 1.day.ago.change(usec: 0), dm_diagnosed_at: 1.day.ago.change(usec: 0)) + expect(mh.valid?).to be_truthy + mh.save! + expect(mh.reload.hypertension).to eq("no") + expect(mh.reload.diabetes).to eq("yes") + expect(mh.reload.htn_diagnosed_at.to_i).to eq(old_htn.to_i) + expect(mh.reload.dm_diagnosed_at.to_i).to eq(old_dm.to_i) + end + end end diff --git a/spec/models/reports/facility_appointment_scheduled_days_spec.rb b/spec/models/reports/facility_appointment_scheduled_days_spec.rb index 7a3010db6f..3adc6d7a71 100644 --- a/spec/models/reports/facility_appointment_scheduled_days_spec.rb +++ b/spec/models/reports/facility_appointment_scheduled_days_spec.rb @@ -236,4 +236,20 @@ expect(described_class.find_by(month_date: Period.current, facility: facility).htn_appts_scheduled_0_to_14_days).to eq 1 expect(described_class.find_by(month_date: Period.current, facility: facility).diabetes_appts_scheduled_0_to_14_days).to eq 1 end + + context "screening" do + it "does not include screened patients" do + facility = create(:facility) + patient = create(:patient, :without_medical_history, recorded_at: 1.month.ago, diagnosed_confirmed_at: nil, assigned_facility: facility) + create(:appointment, + facility: facility, + patient: patient, + scheduled_date: 10.days.from_now, + device_created_at: Time.current) + + RefreshReportingViews.refresh_v2 + + expect(described_class.find_by(month_date: Period.current, facility: facility)).to be_nil + end + end end diff --git a/spec/models/reports/facility_daily_follow_up_and_registration_spec.rb b/spec/models/reports/facility_daily_follow_up_and_registration_spec.rb index e53d7b3921..89f0e6f048 100644 --- a/spec/models/reports/facility_daily_follow_up_and_registration_spec.rb +++ b/spec/models/reports/facility_daily_follow_up_and_registration_spec.rb @@ -180,4 +180,52 @@ expect(daily_statistics_today.daily_follow_ups_htn_or_dm).to eq(1) expect(daily_statistics_yesterday.daily_registrations_htn_or_dm).to eq(1) end + + context "screening" do + let(:six_days_ago) { 6.days.ago.to_date } + let(:four_days_ago) { 4.days.ago.to_date } + it "doesn't include screened patients" do + create(:patient, :without_medical_history, recorded_at: six_days_ago, diagnosed_confirmed_at: nil, assigned_facility: facility) + described_class.refresh + result = described_class.find_by(facility_id: facility, visit_date: six_days_ago) + expect(result.daily_registrations_htn_or_dm).to eq(0) + expect(result.daily_follow_ups_htn_or_dm).to eq(0) + end + + it "includes blood pressure after date of diagnosis" do + diagnosed_patient = create(:patient, :hypertension, recorded_at: six_days_ago, diagnosed_confirmed_at: four_days_ago, registration_user: user, registration_facility: facility) + create(:blood_pressure, patient: diagnosed_patient, user: user, facility: facility, recorded_at: six_days_ago) + create(:blood_pressure, patient: diagnosed_patient, user: user, facility: facility, recorded_at: four_days_ago) + described_class.refresh + expect(described_class.find_by(facility_id: facility, visit_date: six_days_ago).daily_registrations_htn_only).to eq(0) + expect(described_class.find_by(facility_id: facility, visit_date: four_days_ago).daily_registrations_htn_only).to eq(1) + end + + it "includes blood sugars after date of diagnosis" do + diagnosed_patient = create(:patient, :diabetes, recorded_at: six_days_ago, diagnosed_confirmed_at: four_days_ago, registration_user: user, registration_facility: facility) + create(:blood_sugar, patient: diagnosed_patient, user: user, facility: facility, recorded_at: six_days_ago) + create(:blood_sugar, patient: diagnosed_patient, user: user, facility: facility, recorded_at: four_days_ago) + described_class.refresh + expect(described_class.find_by(facility_id: facility, visit_date: six_days_ago).daily_registrations_dm_only).to eq(0) + expect(described_class.find_by(facility_id: facility, visit_date: four_days_ago).daily_registrations_dm_only).to eq(1) + end + + it "includes appointments after date of diagnosis" do + diagnosed_patient = create(:patient, :hypertension, recorded_at: six_days_ago, diagnosed_confirmed_at: four_days_ago, registration_user: user, registration_facility: facility) + create(:appointment, patient: diagnosed_patient, user: user, facility: facility, recorded_at: six_days_ago) + create(:appointment, patient: diagnosed_patient, user: user, facility: facility, recorded_at: four_days_ago) + described_class.refresh + expect(described_class.find_by(facility_id: facility, visit_date: six_days_ago).daily_registrations_htn_only).to eq(0) + expect(described_class.find_by(facility_id: facility, visit_date: four_days_ago).daily_registrations_htn_only).to eq(1) + end + + it "includes prescriptions after date of diagnosis" do + diagnosed_patient = create(:patient, :hypertension, recorded_at: six_days_ago, diagnosed_confirmed_at: four_days_ago, registration_user: user, registration_facility: facility) + create(:prescription_drug, patient: diagnosed_patient, user: user, facility: facility, recorded_at: six_days_ago) + create(:prescription_drug, patient: diagnosed_patient, user: user, facility: facility, recorded_at: four_days_ago) + described_class.refresh + expect(described_class.find_by(facility_id: facility, visit_date: six_days_ago).daily_registrations_htn_only).to eq(0) + expect(described_class.find_by(facility_id: facility, visit_date: four_days_ago).daily_registrations_htn_only).to eq(1) + end + end end diff --git a/spec/models/reports/overdue_calls_spec.rb b/spec/models/reports/overdue_calls_spec.rb new file mode 100755 index 0000000000..920a314c9c --- /dev/null +++ b/spec/models/reports/overdue_calls_spec.rb @@ -0,0 +1,23 @@ +require "rails_helper" + +describe Reports::OverdueCalls, {type: :model, reporting_spec: true} do + it "includes result of a diagnosed patient" do + facility = create(:facility) + patient = create(:patient, diagnosed_confirmed_at: Date.new(2024, 6, 1)) + user_1 = create(:user, registration_facility: facility) + appointment_1 = create(:appointment, patient: patient, facility: facility, device_created_at: Date.new(2024, 6, 1), user: user_1) + create(:call_result, appointment: appointment_1, device_created_at: Date.new(2024, 6, 1), user: user_1) + RefreshReportingViews.refresh_v2 + expect(described_class.where(patient_id: patient.id, month_date: Date.new(2024, 6, 1)).count).to eq(1) + end + + it "doesn't include results of screened patients" do + facility = create(:facility) + patient = create(:patient, :without_medical_history, diagnosed_confirmed_at: nil) + user_1 = create(:user, registration_facility: facility) + appointment_1 = create(:appointment, patient: patient, facility: facility, device_created_at: Date.new(2024, 6, 1), user: user_1) + create(:call_result, appointment: appointment_1, device_created_at: Date.new(2024, 6, 1), user: user_1) + RefreshReportingViews.refresh_v2 + expect(described_class.where(patient_id: patient.id).count).to eq(0) + end +end diff --git a/spec/models/reports/patient_blood_pressure_spec.rb b/spec/models/reports/patient_blood_pressure_spec.rb new file mode 100755 index 0000000000..716fa3eea1 --- /dev/null +++ b/spec/models/reports/patient_blood_pressure_spec.rb @@ -0,0 +1,64 @@ +require "rails_helper" + +describe Reports::PatientBloodPressure, {type: :model, reporting_spec: true} do + describe "Associations" do + it { should belong_to(:patient) } + end + + around do |example| + freeze_time_for_reporting_specs(example) + end + + around do |example| + with_reporting_time_zone { example.run } + end + + it "has the latest blood sugar details for every month a patient has their blood sugar measured" do + patient = create(:patient) + blood_pressure_1 = create(:blood_pressure, patient: patient, recorded_at: 6.months.ago) + blood_pressure_2 = create(:blood_pressure, patient: patient, recorded_at: 2.months.ago) + blood_pressure_3 = create(:blood_pressure, patient: patient, recorded_at: 1.months.ago) + blood_pressure_4 = create(:blood_pressure, patient: patient) + + refresh_views + results = Reports::PatientBloodPressure.all.pluck( + :month_date, + :blood_pressure_id, + :patient_id + ) + expect(results).to include( + [june_2021[:six_months_ago].to_date, blood_pressure_1.id, patient.id], + [june_2021[:two_months_ago].to_date, blood_pressure_2.id, patient.id], + [june_2021[:one_month_ago].to_date, blood_pressure_3.id, patient.id], + [june_2021[:now].to_date, blood_pressure_4.id, patient.id] + ) + end + + context "screening" do + it "doesn't include blood pressures of screened patients" do + patient = create(:patient, diagnosed_confirmed_at: nil) + create(:medical_history, patient: patient, hypertension: "suspected", diabetes: "no") + + refresh_views + results = Reports::PatientBloodPressure.where(patient_id: patient.id) + expect(results).to be_empty + end + + it "calculates the registration indicators based on the diagnosis date" do + patient = create(:patient, recorded_at: Date.new(2024, 5, 1), diagnosed_confirmed_at: Date.new(2024, 6, 1)) + create(:blood_pressure, patient: patient, recorded_at: Date.new(2024, 5, 1)) + create(:blood_pressure, patient: patient, recorded_at: Date.new(2024, 6, 1)) + refresh_views + june_record = described_class.find_by(patient_id: patient.id, month_date: Date.new(2024, 6, 1)) + july_record = described_class.find_by(patient_id: patient.id, month_date: Date.new(2024, 7, 1)) + august_record = described_class.find_by(patient_id: patient.id, month_date: Date.new(2024, 8, 1)) + expect(june_record.patient_registered_at).to eq(patient.diagnosed_confirmed_at) + expect(june_record.months_since_registration).to eq(0) + expect(july_record.months_since_registration).to eq(1) + expect(august_record.months_since_registration).to eq(2) + expect(june_record.quarters_since_registration).to eq(0) + expect(july_record.quarters_since_registration).to eq(1) + expect(august_record.quarters_since_registration).to eq(1) + end + end +end diff --git a/spec/models/reports/patient_blood_sugar_spec.rb b/spec/models/reports/patient_blood_sugar_spec.rb index d66a2cc6cc..b8bcce88a2 100644 --- a/spec/models/reports/patient_blood_sugar_spec.rb +++ b/spec/models/reports/patient_blood_sugar_spec.rb @@ -212,4 +212,32 @@ [june_2021[:one_month_ago].to_date, patients.fourth.id, hba1c_bs_300.id, risk_states[:bs_over_300]] ) end + + context "screening" do + it "doesn't include blood pressures of screened patients" do + patient = create(:patient, :without_medical_history, diagnosed_confirmed_at: nil) + create(:blood_sugar, patient: patient, recorded_at: 1.month.ago) + + refresh_views + results = Reports::PatientBloodSugar.where(patient_id: patient.id) + expect(results).to be_empty + end + + it "calculates the registration indicators based on the diagnosis date" do + patient = create(:patient, recorded_at: Date.new(2024, 5, 1), diagnosed_confirmed_at: Date.new(2024, 6, 1)) + create(:blood_sugar, patient: patient, recorded_at: Date.new(2024, 5, 1)) + create(:blood_sugar, patient: patient, recorded_at: Date.new(2024, 6, 1)) + refresh_views + june_record = described_class.find_by(patient_id: patient.id, month_date: Date.new(2024, 6, 1)) + july_record = described_class.find_by(patient_id: patient.id, month_date: Date.new(2024, 7, 1)) + august_record = described_class.find_by(patient_id: patient.id, month_date: Date.new(2024, 8, 1)) + expect(june_record.patient_registered_at).to eq(patient.diagnosed_confirmed_at) + expect(june_record.months_since_registration).to eq(0) + expect(july_record.months_since_registration).to eq(1) + expect(august_record.months_since_registration).to eq(2) + expect(june_record.quarters_since_registration).to eq(0) + expect(july_record.quarters_since_registration).to eq(1) + expect(august_record.quarters_since_registration).to eq(1) + end + end end diff --git a/spec/models/reports/patient_follow_up_spec.rb b/spec/models/reports/patient_follow_up_spec.rb index 85234030df..2036927d6f 100644 --- a/spec/models/reports/patient_follow_up_spec.rb +++ b/spec/models/reports/patient_follow_up_spec.rb @@ -173,4 +173,44 @@ expect(described_class.count).to eq(1) end + + context "screening" do + it "doesn't include screened patients" do + screened_patient = create(:patient, :without_medical_history, diagnosed_confirmed_at: nil) + described_class.refresh + expect(described_class.where(patient_id: screened_patient.id).count).to eq(0) + end + + it "includes blood pressure after date of diagnosis" do + diagnosed_patient = create(:patient, :hypertension, recorded_at: june_2021[:three_months_ago], diagnosed_confirmed_at: june_2021[:two_months_ago], registration_user: user, registration_facility: facility) + _old_bp = create(:blood_pressure, patient: diagnosed_patient, user: user, facility: facility, recorded_at: june_2021[:three_months_ago]) + new_bp = create(:blood_pressure, patient: diagnosed_patient, user: user, facility: facility, recorded_at: june_2021[:now]) + described_class.refresh + expect(described_class.where(patient: diagnosed_patient).pluck(:visit_id)).to eq([new_bp.id]) + end + + it "includes blood sugars after date of diagnosis" do + diagnosed_patient = create(:patient, :hypertension, recorded_at: june_2021[:three_months_ago], diagnosed_confirmed_at: june_2021[:two_months_ago], registration_user: user, registration_facility: facility) + _old_bs = create(:blood_sugar, patient: diagnosed_patient, user: user, facility: facility, recorded_at: june_2021[:three_months_ago]) + new_bs = create(:blood_sugar, patient: diagnosed_patient, user: user, facility: facility, recorded_at: june_2021[:now]) + described_class.refresh + expect(described_class.where(patient: diagnosed_patient).pluck(:visit_id)).to eq([new_bs.id]) + end + + it "includes appointments after date of diagnosis" do + diagnosed_patient = create(:patient, :hypertension, recorded_at: june_2021[:three_months_ago], diagnosed_confirmed_at: june_2021[:two_months_ago], registration_user: user, registration_facility: facility) + _old_appointment = create(:appointment, patient: diagnosed_patient, user: user, facility: facility, recorded_at: june_2021[:three_months_ago]) + new_appointment = create(:appointment, patient: diagnosed_patient, user: user, facility: facility, recorded_at: june_2021[:now]) + described_class.refresh + expect(described_class.where(patient: diagnosed_patient).pluck(:visit_id)).to eq([new_appointment.id]) + end + + it "includes prescriptions after date of diagnosis" do + diagnosed_patient = create(:patient, :hypertension, recorded_at: june_2021[:three_months_ago], diagnosed_confirmed_at: june_2021[:two_months_ago], registration_user: user, registration_facility: facility) + _old_prescription = create(:appointment, patient: diagnosed_patient, user: user, facility: facility, recorded_at: june_2021[:three_months_ago]) + new_prescription = create(:appointment, patient: diagnosed_patient, user: user, facility: facility, recorded_at: june_2021[:now]) + described_class.refresh + expect(described_class.where(patient: diagnosed_patient).pluck(:visit_id)).to eq([new_prescription.id]) + end + end end diff --git a/spec/models/reports/patient_state_spec.rb b/spec/models/reports/patient_state_spec.rb index 77f866172f..75f06ef520 100644 --- a/spec/models/reports/patient_state_spec.rb +++ b/spec/models/reports/patient_state_spec.rb @@ -103,8 +103,8 @@ context "ltfu tests ported from patient_spec.rb" do it "picks up the beginning interval of ltfu for 12 months ago correctly" do - under_care_patient = create(:patient, recorded_at: june_2021[:long_ago]) - ltfu_patient = create(:patient, recorded_at: june_2021[:long_ago]) + under_care_patient = create(:patient, recorded_at: june_2021[:long_ago], diagnosed_confirmed_at: june_2021[:long_ago]) + ltfu_patient = create(:patient, recorded_at: june_2021[:long_ago], diagnosed_confirmed_at: june_2021[:long_ago]) create(:blood_pressure, :with_encounter, patient: under_care_patient, recorded_at: june_2021[:under_12_months_ago]) create(:blood_pressure, :with_encounter, patient: ltfu_patient, recorded_at: june_2021[:over_12_months_ago]) @@ -128,8 +128,8 @@ end it "picks up the ending interval of ltfu for now correctly" do - under_care_patient = create(:patient, recorded_at: june_2021[:long_ago]) - ltfu_patient = create(:patient, recorded_at: june_2021[:long_ago]) + under_care_patient = create(:patient, recorded_at: june_2021[:long_ago], diagnosed_confirmed_at: june_2021[:long_ago]) + ltfu_patient = create(:patient, recorded_at: june_2021[:long_ago], diagnosed_confirmed_at: june_2021[:long_ago]) create(:blood_pressure, :with_encounter, patient: under_care_patient, recorded_at: june_2021[:end_of_month] - 1.minute) create(:blood_pressure, :with_encounter, patient: ltfu_patient, recorded_at: june_2021[:end_of_month] + 1.minute) @@ -153,8 +153,8 @@ end it "registration cutoffs for a year ago" do - under_care_patient = create(:patient, recorded_at: june_2021[:under_12_months_ago]) - ltfu_patient = create(:patient, recorded_at: june_2021[:over_12_months_ago]) + under_care_patient = create(:patient, recorded_at: june_2021[:under_12_months_ago], diagnosed_confirmed_at: june_2021[:under_12_months_ago]) + ltfu_patient = create(:patient, recorded_at: june_2021[:over_12_months_ago], diagnosed_confirmed_at: june_2021[:over_12_months_ago]) RefreshReportingViews.refresh_v2 with_reporting_time_zone do @@ -435,10 +435,10 @@ describe "months_since_registration" do it "computes it correctly" do - patient_1 = create(:patient, recorded_at: june_2021[:under_12_months_ago]) - patient_2 = create(:patient, recorded_at: june_2021[:over_12_months_ago]) - patient_3 = create(:patient, recorded_at: june_2021[:now]) - patient_4 = create(:patient, recorded_at: june_2021[:over_3_months_ago]) + patient_1 = create(:patient, recorded_at: june_2021[:under_12_months_ago], diagnosed_confirmed_at: june_2021[:under_12_months_ago]) + patient_2 = create(:patient, recorded_at: june_2021[:over_12_months_ago], diagnosed_confirmed_at: june_2021[:over_12_months_ago]) + patient_3 = create(:patient, recorded_at: june_2021[:now], diagnosed_confirmed_at: june_2021[:now]) + patient_4 = create(:patient, recorded_at: june_2021[:over_3_months_ago], diagnosed_confirmed_at: june_2021[:over_3_months_ago]) RefreshReportingViews.refresh_v2 with_reporting_time_zone do @@ -452,10 +452,10 @@ describe "quarters_since_registration" do it "computes it correctly" do - patient_1 = create(:patient, recorded_at: june_2021[:now] - 23.months) - patient_2 = create(:patient, recorded_at: june_2021[:now] - 13.months) - patient_3 = create(:patient, recorded_at: june_2021[:now] - 4.months) - patient_4 = create(:patient, recorded_at: june_2021[:now]) + patient_1 = create(:patient, recorded_at: june_2021[:now] - 23.months, diagnosed_confirmed_at: june_2021[:now] - 23.months) + patient_2 = create(:patient, recorded_at: june_2021[:now] - 13.months, diagnosed_confirmed_at: june_2021[:now] - 13.months) + patient_3 = create(:patient, recorded_at: june_2021[:now] - 4.months, diagnosed_confirmed_at: june_2021[:now] - 4.months) + patient_4 = create(:patient, recorded_at: june_2021[:now], diagnosed_confirmed_at: june_2021[:now]) RefreshReportingViews.refresh_v2 with_reporting_time_zone do @@ -587,7 +587,7 @@ def patient_states(patient, from: nil, to: nil) # 10 months ago controlled bp taken # 8 months ago visit but no bp (drugs) # 5 months ago uncontrolled bp taken - patient = create(:patient, recorded_at: two_years_ago) + patient = create(:patient, recorded_at: two_years_ago, diagnosed_confirmed_at: two_years_ago) create(:bp_with_encounter, :under_control, patient: patient, recorded_at: ten_months_ago) create(:prescription_drug, patient: patient, device_created_at: eight_months_ago) create(:bp_with_encounter, :hypertensive, patient: patient, recorded_at: five_months_ago) @@ -648,7 +648,7 @@ def patient_states(patient, from: nil, to: nil) end it "contains the details of the latest blood sugar measurement for a given month" do - patient = create(:patient, recorded_at: 3.months.ago) + patient = create(:patient, recorded_at: 3.months.ago, diagnosed_confirmed_at: 3.months.ago) blood_sugar_1 = create(:blood_sugar, patient: patient, recorded_at: 1.months.ago) blood_sugar_2 = create(:blood_sugar, patient: patient, recorded_at: 2.days.ago) blood_sugar_3 = create(:blood_sugar, patient: patient, recorded_at: Time.now) @@ -670,7 +670,7 @@ def patient_states(patient, from: nil, to: nil) end it "contains the details of the latest blood sugar measurement from previous months if there is none in the given month" do - patient = create(:patient, recorded_at: 6.months.ago) + patient = create(:patient, recorded_at: 6.months.ago, diagnosed_confirmed_at: 6.months.ago) blood_sugar_1 = create(:blood_sugar, patient: patient, recorded_at: 4.months.ago) blood_sugar_2 = create(:blood_sugar, patient: patient, recorded_at: 2.months.ago) blood_sugar_3 = create(:blood_sugar, patient: patient, recorded_at: Time.now) @@ -689,7 +689,7 @@ def patient_states(patient, from: nil, to: nil) end it "contains the blood sugar risk state based on the latest blood sugar measurement" do - patients = create_list(:patient, 4, recorded_at: 6.months.ago) + patients = create_list(:patient, 4, recorded_at: 6.months.ago, diagnosed_confirmed_at: 6.months.ago) [[patients.first, :random], [patients.second, :post_prandial], [patients.third, :fasting], @@ -733,7 +733,7 @@ def patient_states(patient, from: nil, to: nil) end it "contains a count of months and quarters since the latest blood sugar measurement" do - patient = create(:patient, recorded_at: 6.months.ago) + patient = create(:patient, recorded_at: 6.months.ago, diagnosed_confirmed_at: 6.months.ago) blood_sugar_1 = create(:blood_sugar, patient: patient, recorded_at: 4.months.ago) blood_sugar_2 = create(:blood_sugar, patient: patient, recorded_at: 1.months.ago) blood_sugar_3 = create(:blood_sugar, patient: patient, recorded_at: Time.now) @@ -874,4 +874,81 @@ def patient_states(patient, from: nil, to: nil) described_class.partitioned_refresh(refresh_month) end end + + context "screening" do + it "doesn't consider the patient if it's only screened" do + diagnosed_patient = create(:patient, recorded_at: Date.new(2024, 5, 1), diagnosed_confirmed_at: Date.new(2024, 6, 1)) + screened_patient = create(:patient, recorded_at: Date.new(2024, 6, 1), diagnosed_confirmed_at: nil, medical_history: build(:medical_history, :hypertension_suspected, :diabetes_suspected)) + described_class.partitioned_refresh(Date.new(2024, 6, 1)) + patient_ids = described_class.where(month_date: Date.new(2024, 6, 1)).pluck(:patient_id) + expect(patient_ids).to include(diagnosed_patient.id) + expect(patient_ids).not_to include(screened_patient.id) + end + + it "prefers the most-recent medical_history when determining screened vs diagnosed (latest = suspected -> excluded)" do + patient = create( + :patient, + recorded_at: Date.new(2024, 6, 1), + diagnosed_confirmed_at: nil, + medical_history: build(:medical_history, :hypertension_suspected, :diabetes_suspected) + ) + + create( + :medical_history, + patient: patient, + device_updated_at: 1.day.ago, + device_created_at: 1.day.ago - 1.minute, + hypertension: "suspected", + htn_diagnosed_at: nil + ) + + described_class.partitioned_refresh(Date.new(2024, 6, 1)) + patient_ids = described_class.where(month_date: Date.new(2024, 6, 1)).pluck(:patient_id) + + expect(patient_ids).not_to include(patient.id) + end + + it "prefers the most-recent medical_history and includes patient when the latest MH has a diagnosis date (latest = diagnosed -> included)" do + patient = create( + :patient, + recorded_at: Date.new(2024, 6, 1), + diagnosed_confirmed_at: nil, + medical_history: build(:medical_history, :hypertension_suspected, :diabetes_suspected) + ) + + diagnosed_date = Date.new(2024, 5, 1) + create( + :medical_history, + patient: patient, + device_updated_at: 1.day.ago, + device_created_at: 1.day.ago - 1.minute, + hypertension: "yes", + htn_diagnosed_at: diagnosed_date + ) + + described_class.partitioned_refresh(Date.new(2024, 6, 1)) + patient_ids = described_class.where(month_date: Date.new(2024, 6, 1)).pluck(:patient_id) + + expect(patient_ids).to include(patient.id) + + row = described_class.find_by(patient_id: patient.id, month_date: Date.new(2024, 6, 1)) + expect(row.diagnosed_confirmed_at.to_date).to eq(diagnosed_date) + end + + it "calculates registration indicators from the date of first diagnosis" do + diagnosed_patient = create(:patient, recorded_at: Date.new(2024, 5, 1), diagnosed_confirmed_at: Date.new(2024, 6, 1)) + described_class.partitioned_refresh(Date.new(2024, 6, 1)) + described_class.partitioned_refresh(Date.new(2024, 7, 1)) + described_class.partitioned_refresh(Date.new(2024, 8, 1)) + june_record = described_class.find_by(patient_id: diagnosed_patient.id, month_date: Date.new(2024, 6, 1)) + july_record = described_class.find_by(patient_id: diagnosed_patient.id, month_date: Date.new(2024, 7, 1)) + august_record = described_class.find_by(patient_id: diagnosed_patient.id, month_date: Date.new(2024, 8, 1)) + expect(june_record.months_since_registration).to eq(0) + expect(july_record.months_since_registration).to eq(1) + expect(august_record.months_since_registration).to eq(2) + expect(june_record.quarters_since_registration).to eq(0) + expect(july_record.quarters_since_registration).to eq(1) + expect(august_record.quarters_since_registration).to eq(1) + end + end end diff --git a/spec/models/reports/patient_visit_spec.rb b/spec/models/reports/patient_visit_spec.rb index 442e085d07..34fbd38bad 100644 --- a/spec/models/reports/patient_visit_spec.rb +++ b/spec/models/reports/patient_visit_spec.rb @@ -116,4 +116,32 @@ end end end + + context "screening" do + it "does not include screened patients" do + patient = create(:patient, :without_medical_history, diagnosed_confirmed_at: nil) + create(:appointment, patient: patient, recorded_at: 1.month.ago) + + described_class.refresh + expect(described_class.find_by(patient_id: patient.id)).to be_nil + end + + it "calculates the registration indicators based on the diagnosis date" do + patient = create(:patient, recorded_at: Date.new(2024, 5, 1), diagnosed_confirmed_at: Date.new(2024, 6, 1)) + create(:appointment, patient: patient, recorded_at: Date.new(2024, 5, 1)) + create(:appointment, patient: patient, recorded_at: Date.new(2024, 6, 1)) + + described_class.refresh + june_record = described_class.find_by(patient_id: patient.id, month_date: Date.new(2024, 6, 1)) + july_record = described_class.find_by(patient_id: patient.id, month_date: Date.new(2024, 7, 1)) + august_record = described_class.find_by(patient_id: patient.id, month_date: Date.new(2024, 8, 1)) + expect(june_record.patient_recorded_at).to eq(patient.diagnosed_confirmed_at) + expect(june_record.months_since_registration).to eq(0) + expect(july_record.months_since_registration).to eq(1) + expect(august_record.months_since_registration).to eq(2) + expect(june_record.quarters_since_registration).to eq(0) + expect(july_record.quarters_since_registration).to eq(1) + expect(august_record.quarters_since_registration).to eq(1) + end + end end diff --git a/spec/models/reports/prescription_spec.rb b/spec/models/reports/prescription_spec.rb index f5c4120c2f..6858b0fd6e 100644 --- a/spec/models/reports/prescription_spec.rb +++ b/spec/models/reports/prescription_spec.rb @@ -151,4 +151,18 @@ def add_drug(patient, name, dosage, time, do_not_delete: false) end end end + + context "screening" do + it "doesn't include screened patients" do + DrugLookup::MedicinePurpose.create(name: "Amlodipine", hypertension: true, diabetes: false) + DrugLookup::RawToCleanMedicine.create({raw_name: "Amlo", raw_dosage: "5mg", rxcui: 123}) + DrugLookup::CleanMedicineToDosage.create({medicine: "Amlodipine", dosage: 5, rxcui: 123}) + + patient = create(:patient, :without_medical_history, recorded_at: Date.new(2024, 6, 1), diagnosed_confirmed_at: nil) + add_drug(patient, "Amlo", "5mg", Date.new(2024, 7, 1)) + + described_class.refresh + expect(described_class.find_by(patient: patient)).to be_nil + end + end end diff --git a/spec/requests/api/v3/patient_sync_spec.rb b/spec/requests/api/v3/patient_sync_spec.rb index 3d169bcb06..18c00cefe1 100644 --- a/spec/requests/api/v3/patient_sync_spec.rb +++ b/spec/requests/api/v3/patient_sync_spec.rb @@ -6,7 +6,7 @@ let(:sync_route) { "/api/v3/patients/sync" } - let(:build_payload) { -> { build_patient_payload(FactoryBot.build(:patient, registration_facility: request_user.facility)) } } + let(:build_payload) { -> { build_patient_payload(FactoryBot.build(:patient, registration_facility: request_user.facility, diagnosed_confirmed_at: nil)) } } let(:build_invalid_payload) { -> { build_invalid_patient_payload } } let(:update_payload) { ->(record) { updated_patient_payload record } } diff --git a/spec/services/bulk_api_import/fhir_condition_importer_spec.rb b/spec/services/bulk_api_import/fhir_condition_importer_spec.rb index 9176145ecb..673e1d93e3 100644 --- a/spec/services/bulk_api_import/fhir_condition_importer_spec.rb +++ b/spec/services/bulk_api_import/fhir_condition_importer_spec.rb @@ -61,7 +61,7 @@ ).import.slice(:hypertension, :diabetes) expect(first_medical_history_state).to include(diabetes: "no", hypertension: "yes") - expect(second_medical_history_state).to include(diabetes: "yes", hypertension: "yes") + expect(second_medical_history_state.symbolize_keys).to include(diabetes: "yes", hypertension: "yes") end end diff --git a/spec/transformers/api/v3/medical_history_transformer_spec.rb b/spec/transformers/api/v3/medical_history_transformer_spec.rb index 13e6206a02..446a09d41d 100644 --- a/spec/transformers/api/v3/medical_history_transformer_spec.rb +++ b/spec/transformers/api/v3/medical_history_transformer_spec.rb @@ -34,6 +34,22 @@ end end + context "when hypertension is 'suspected'" do + let(:medical_history_params) do + { + id: SecureRandom.uuid, + patient_id: SecureRandom.uuid, + hypertension: "suspected", + diagnosed_with_hypertension: "suspected" + } + end + + it "passes along the suspected hypertension values" do + expect(transformed_params[:hypertension]).to eq("suspected") + expect(transformed_params[:diagnosed_with_hypertension]).to eq("suspected") + end + end + context "when hypertension is not provided" do let(:medical_history_params) do { @@ -48,5 +64,19 @@ expect(transformed_params[:diagnosed_with_hypertension]).to eq("yes") end end + + context "when diabetes is 'suspected'" do + let(:medical_history_params) do + { + id: SecureRandom.uuid, + patient_id: SecureRandom.uuid, + diabetes: "suspected" + } + end + + it "passes along the suspected diabetes values" do + expect(transformed_params[:diabetes]).to eq("suspected") + end + end end end diff --git a/spec/utils.rb b/spec/utils.rb index 28960440bc..af60244d89 100644 --- a/spec/utils.rb +++ b/spec/utils.rb @@ -1,6 +1,6 @@ class Hash def with_int_timestamps - ts_keys = %w[recorded_at created_at updated_at device_created_at device_updated_at age_updated_at otp_expires_at deleted_at requested_at] + ts_keys = %w[recorded_at created_at updated_at device_created_at device_updated_at age_updated_at otp_expires_at deleted_at requested_at diagnosed_confirmed_at] each_pair do |key, value| if ts_keys.include?(key) && value.present? self[key] = value.to_time.to_i