Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Sync To User performance (batch 2) #1897

Merged
merged 13 commits into from
Dec 23, 2020
Merged
2 changes: 1 addition & 1 deletion app/controllers/api/v3/blood_sugars_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def sync_to_user

private

def region_records
def model_sync_scope
super.for_v3
end

Expand Down
15 changes: 6 additions & 9 deletions app/controllers/api/v3/patients_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,19 @@ def request_metadata
{request_user_id: current_user.id, request_facility_id: current_facility.id}
end

def region_records
super
.includes(:address, :phone_numbers, :business_identifiers)
end

def current_facility_records
region_records
.where(registration_facility: current_facility)
model_sync_scope
.where(id: current_facility.syncable_patients.pluck(:id))
.updated_on_server_since(current_facility_processed_since, limit)
end

def other_facility_records
other_facilities_limit = limit - current_facility_records.count
other_patient_records =
current_sync_region.syncable_patients.pluck(:id) - current_facility.syncable_patients.pluck(:id)

region_records
.where.not(registration_facility: current_facility)
model_sync_scope
.where(id: other_patient_records)
.updated_on_server_since(other_facilities_processed_since, other_facilities_limit)
end

Expand Down
25 changes: 11 additions & 14 deletions app/controllers/concerns/api/v3/sync_to_user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,30 @@ module Api::V3::SyncToUser
extend ActiveSupport::Concern

included do
def region_records
model = controller_name.classify.constantize
model.syncable_to_region(current_sync_region)
end

def current_facility_records
region_records
.where(patient: prioritized_patients)
model_sync_scope
.where(patient: current_facility.syncable_patients.pluck(:id))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would a couple semantic methods help with readability?

def current_facility_patient_ids
  @current_facility_patient_ids ||= current_facility.syncable_patients.pluck(:id)
end

def other_facility_patient_ids
  @other_facility_patient_ids ||= current_sync_region.syncable_patients.pluck(:id) - current_facility_patient_ids
end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They might, I can play around with this. But our general approach was to reduce the number of methods in this mixin. Every method that we add, is an opportunity for someone to override it in some random place in some controller that inherits from it. The thinner this mixin is in terms of pure method volume, the cleaner the internal API is.

I'll take a shot at this and see if it helps anyway. Will respond back here.

.updated_on_server_since(current_facility_processed_since, limit)
end

def other_facility_records
other_facilities_limit = limit - current_facility_records.count
other_facilities_limit = limit - current_facility_records.size
other_patient_records =
current_sync_region.syncable_patients.pluck(:id) - current_facility.syncable_patients.pluck(:id)

region_records
.where.not(patient: prioritized_patients)
model_sync_scope
.where(patient: other_patient_records)
.updated_on_server_since(other_facilities_processed_since, other_facilities_limit)
end

private

def records_to_sync
current_facility_records + other_facility_records
def model_sync_scope
controller_name.classify.constantize.for_sync
Copy link
Contributor

@rsanheim rsanheim Dec 17, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I understand why you are returning a AR scope here instead of the actual model, but its a confusing mismatch with the name and conventions around something named model. Maybe rename the method to something like scope to better represent that its the base scope for other methods to build off of? Or maybe region_scope, sync_scope, default_sync_scope ?

Copy link
Contributor Author

@kitallis kitallis Dec 18, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I wasn't happy with continuing to calling it model. We'd removed model earlier because I disliked its hanging presence with invisible meanings. But I think sync_scope makes sense 👍🏼

end

def prioritized_patients
current_facility.registered_patients.with_discarded
def records_to_sync
current_facility_records + other_facility_records
end

def processed_until(records)
Expand Down
4 changes: 1 addition & 3 deletions app/models/appointment.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,7 @@ class Appointment < ApplicationRecord
validates :device_created_at, presence: true
validates :device_updated_at, presence: true

scope :syncable_to_region, ->(region) {
with_discarded.where(patient: Patient.syncable_to_region(region))
}
scope :for_sync, -> { with_discarded }

def self.all_overdue
where(status: "scheduled")
Expand Down
4 changes: 1 addition & 3 deletions app/models/blood_pressure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ class BloodPressure < ApplicationRecord
.where(arel_table[:diastolic].lt(THRESHOLDS[:hypertensive][:diastolic]))
}

scope :syncable_to_region, ->(region) {
with_discarded.where(patient: Patient.syncable_to_region(region))
}
scope :for_sync, -> { with_discarded }

def critical?
systolic >= THRESHOLDS[:critical][:systolic] || diastolic >= THRESHOLDS[:critical][:diastolic]
Expand Down
5 changes: 1 addition & 4 deletions app/models/blood_sugar.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,7 @@ class BloodSugar < ApplicationRecord
V3_TYPES = %i[random post_prandial fasting].freeze

scope :for_v3, -> { where(blood_sugar_type: V3_TYPES) }

scope :syncable_to_region, ->(region) {
with_discarded.where(patient: Patient.syncable_to_region(region))
}
scope :for_sync, -> { with_discarded }

THRESHOLDS = {
high: {random: 300,
Expand Down
4 changes: 1 addition & 3 deletions app/models/encounter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ class Encounter < ApplicationRecord
has_many :blood_pressures, through: :observations, source: :observable, source_type: "BloodPressure"
has_many :blood_sugars, through: :observations, source: :observable, source_type: "BloodSugar"

scope :syncable_to_region, ->(region) {
with_discarded.where(patient: Patient.syncable_to_region(region))
}
scope :for_sync, -> { with_discarded }

def self.generate_id(facility_id, patient_id, encountered_on)
UUIDTools::UUID
Expand Down
4 changes: 4 additions & 0 deletions app/models/facility.rb
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,8 @@ def valid_block
errors.add(:zone, "not present in the facility group")
end
end

def syncable_patients
registered_patients.with_discarded
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a bit dissonant? "Syncable" in general means you are registered in, assigned to, or plan to visit. We're using a simplified definition here because it doesn't really matter when used for facility prioritization. However, does this set us up to do things like facility-level sync incorrectly?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To address the dissonance, I suspect we'd either:

  • update the query here to include all three clauses - This doesn't seem correct because it doesn't maintain feature parity in the sync API, and is also less performant probably
  • update the name of this method? - Something along the lines of prioritized_patients or something? Making it clear that this boundary is not a facility-level sync boundary, but a looser boundary within another block-or-FG-level sync?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

prioritized_patients works I think 👍🏼

end
4 changes: 1 addition & 3 deletions app/models/medical_history.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ class MedicalHistory < ApplicationRecord
enum hypertension: MEDICAL_HISTORY_ANSWERS, _prefix: true
enum diagnosed_with_hypertension: MEDICAL_HISTORY_ANSWERS, _prefix: true

scope :syncable_to_region, ->(region) {
with_discarded.where(patient: Patient.syncable_to_region(region))
}
scope :for_sync, -> { with_discarded }

def indicates_hypertension_risk?
prior_heart_attack_boolean || prior_stroke_boolean
Expand Down
3 changes: 2 additions & 1 deletion app/models/patient.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ class Patient < ApplicationRecord

attribute :call_result, :string

scope :syncable_to_region, ->(region) { region.syncable_patients }
scope :with_nested_sync_resources, -> { includes(:address, :phone_numbers, :business_identifiers) }
scope :for_sync, -> { with_discarded.with_nested_sync_resources }
scope :search_by_address, ->(term) { joins(:address).merge(Address.search_by_street_or_village(term)) }
scope :with_diabetes, -> { joins(:medical_history).merge(MedicalHistory.diabetes_yes) }
scope :with_hypertension, -> { joins(:medical_history).merge(MedicalHistory.hypertension_yes) }
Expand Down
4 changes: 1 addition & 3 deletions app/models/prescription_drug.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ class PrescriptionDrug < ApplicationRecord
validates :is_protocol_drug, inclusion: {in: [true, false]}
validates :is_deleted, inclusion: {in: [true, false]}

scope :syncable_to_region, ->(region) {
with_discarded.where(patient: Patient.syncable_to_region(region))
}
scope :for_sync, -> { with_discarded }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we add a couple tiny tests for this scope across these models?


def self.prescribed_as_of(date)
where("device_created_at <= ?", date.end_of_day)
Expand Down
4 changes: 2 additions & 2 deletions app/models/region.rb
Original file line number Diff line number Diff line change
Expand Up @@ -88,10 +88,10 @@ def syncable_patients
case region_type
when "block"
registered_patients.with_discarded
.union(assigned_patients.with_discarded)
.or(assigned_patients.with_discarded)
.union(appointed_patients.with_discarded)
harimohanraj89 marked this conversation as resolved.
Show resolved Hide resolved
else
registered_patients
registered_patients.with_discarded
end
end

Expand Down
25 changes: 0 additions & 25 deletions spec/models/appointment_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,31 +104,6 @@
expect(described_class.eligible_for_reminders(days_overdue: 3)).to be_empty
end
end

describe ".syncable_to_region" do
it "returns all patients registered in the region" do
facility_group = create(:facility_group)
facility = create(:facility, facility_group: facility_group)
patient = create(:patient)
other_patient = create(:patient)

allow(Patient).to receive(:syncable_to_region).with(facility_group).and_return([patient])

appointments = [
create(:appointment, patient: patient, facility: facility),
create(:appointment, patient: patient, facility: facility).tap(&:discard),
create(:appointment, patient: patient)
]

_other_appointments = [
create(:appointment, patient: other_patient, facility: facility),
create(:appointment, patient: other_patient, facility: facility).tap(&:discard),
create(:appointment, patient: other_patient)
]

expect(Appointment.syncable_to_region(facility_group)).to contain_exactly(*appointments)
end
end
end

context "For discarded patients" do
Expand Down
25 changes: 0 additions & 25 deletions spec/models/blood_pressure_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,31 +39,6 @@
expect(BloodPressure.under_control).not_to include(bp_high_systolic, bp_high_diastolic, bp_high_both)
end
end

describe ".syncable_to_region" do
it "returns all patients registered in the region" do
facility_group = create(:facility_group)
facility = create(:facility, facility_group: facility_group)
patient = create(:patient)
other_patient = create(:patient)

allow(Patient).to receive(:syncable_to_region).with(facility_group).and_return([patient])

blood_pressures = [
create(:blood_pressure, patient: patient, facility: facility),
create(:blood_pressure, patient: patient, facility: facility).tap(&:discard),
create(:blood_pressure, patient: patient)
]

_other_blood_pressures = [
create(:blood_pressure, patient: other_patient, facility: facility),
create(:blood_pressure, patient: other_patient, facility: facility).tap(&:discard),
create(:blood_pressure, patient: other_patient)
]

expect(BloodPressure.syncable_to_region(facility_group)).to contain_exactly(*blood_pressures)
end
end
end

context "utility methods" do
Expand Down
25 changes: 0 additions & 25 deletions spec/models/blood_sugar_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,30 +49,5 @@
expect(BloodSugar.for_v3.count).to eq 3
end
end

describe ".syncable_to_region" do
it "returns all patients registered in the region" do
facility_group = create(:facility_group)
facility = create(:facility, facility_group: facility_group)
patient = create(:patient)
other_patient = create(:patient)

allow(Patient).to receive(:syncable_to_region).with(facility_group).and_return([patient])

blood_sugars = [
create(:blood_sugar, patient: patient, facility: facility),
create(:blood_sugar, patient: patient, facility: facility).tap(&:discard),
create(:blood_sugar, patient: patient)
]

_other_blood_sugars = [
create(:blood_sugar, patient: other_patient, facility: facility),
create(:blood_sugar, patient: other_patient, facility: facility).tap(&:discard),
create(:blood_sugar, patient: other_patient)
]

expect(BloodSugar.syncable_to_region(facility_group)).to contain_exactly(*blood_sugars)
end
end
end
end
27 changes: 0 additions & 27 deletions spec/models/encounter_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,31 +32,4 @@
expect(id_1).to eq(id_2)
end
end

describe "Scopes" do
describe ".syncable_to_region" do
it "returns all patients registered in the region" do
facility_group = create(:facility_group)
facility = create(:facility, facility_group: facility_group)
patient = create(:patient)
other_patient = create(:patient)

allow(Patient).to receive(:syncable_to_region).with(facility_group).and_return([patient])

encounters = [
create(:encounter, patient: patient, facility: facility),
create(:encounter, patient: patient, facility: facility).tap(&:discard),
create(:encounter, patient: patient)
]

_other_encounters = [
create(:encounter, patient: other_patient, facility: facility),
create(:encounter, patient: other_patient, facility: facility).tap(&:discard),
create(:encounter, patient: other_patient)
]

expect(Encounter.syncable_to_region(facility_group)).to contain_exactly(*encounters)
end
end
end
end
25 changes: 0 additions & 25 deletions spec/models/medical_history_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,31 +10,6 @@
it { should validate_presence_of(:device_updated_at) }
end

describe "Scopes" do
describe ".syncable_to_region" do
it "returns all patients registered in the region" do
facility_group = create(:facility_group)
patient = create(:patient)
other_patient = create(:patient)

allow(Patient).to receive(:syncable_to_region).with(facility_group).and_return([patient])

MedicalHistory.destroy_all
medical_histories = [
create(:medical_history, patient: patient),
create(:medical_history, patient: patient).tap(&:discard)
]

_other_medical_histories = [
create(:medical_history, patient: other_patient),
create(:medical_history, patient: other_patient).tap(&:discard)
]

expect(MedicalHistory.syncable_to_region(facility_group)).to contain_exactly(*medical_histories)
end
end
end

describe "Behavior" do
it_behaves_like "a record that is deletable"
end
Expand Down
24 changes: 0 additions & 24 deletions spec/models/patient_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -312,30 +312,6 @@
expect(Patient.not_contacted).not_to include(patient_could_not_be_contacted)
end
end

describe ".syncable_to_region" do
it "returns all patients registered in the region" do
facility_group = create(:facility_group)
other_facility_group = create(:facility_group)

facility = create(:facility, facility_group: facility_group)
other_facility = create(:facility, facility_group: other_facility_group)

patients = [
create(:patient, registration_facility: facility),
create(:patient, registration_facility: facility).tap(&:discard),
create(:patient, registration_facility: facility, assigned_facility: other_facility)
]

_other_patients = [
create(:patient, registration_facility: other_facility),
create(:patient, registration_facility: other_facility).tap(&:discard),
create(:patient, registration_facility: other_facility, assigned_facility: facility)
]

expect(Patient.syncable_to_region(facility_group)).to contain_exactly(*patients)
end
end
end

context "Utility methods" do
Expand Down
Loading