Giter Club home page Giter Club logo

simple-server's People

Contributors

abnios avatar arku avatar benryder1988 avatar bprerana avatar claudiovallejo avatar danysam avatar dburka avatar dependabot[bot] avatar divs1210 avatar govindkrjoshi avatar greysteil avatar harimohanraj89 avatar jamiecarter7 avatar kitallis avatar kpethtel avatar nas887 avatar ogirginc avatar olttwa avatar prabhanshuguptagit avatar priyangapkini avatar qptr avatar roypeter avatar rsanheim avatar solracdelsol avatar ssrihari avatar tfidfwastaken avatar timcheadle avatar timmyjose avatar transifex-integration[bot] avatar vkrmis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

simple-server's Issues

Sync To User perf improvements (part 2)

We recently put up some sync performance fixes to ensure that block-level syncs are fast, for a reasonably large load, to allow for a safe incremental release. After pushing these changes to production, we discovered that the improvements actually negatively impacted regular FacilityGroup syncing.

This impact wasn't much, but it was enough that we decided to revert the changes.

what actually causes the slowness

It lies in a couple of distinct places. The first one is the query that fetches the records for "other facilities" (not the current facility) and the second is how many times this query is called during a request-response cycle.

The 1st one is more nuanced than just write a faster query. In this diff from the revert, notice the reverted changes (in red),

Screenshot 2020-12-29 at 2 54 23 PM

This piece of code was one of the bottlenecks,

current_sync_region.syncable_patients.pluck(:id) - current_facility.prioritized_patients.pluck(:id)

This causes a kind of an "inversion" point where up until a certain number of syncable_patients are reached, this is faster for most of syncs. However, for FG, this number is typically very large and causes it to be slower than the original query itself.

After running some benchmarks, we discovered that the inversion point is somewhere around 4-5k syncable_patients.

This stands to reason that if we simply branched the code by counting the syncable_patients, we'd achieve good results for both scenarios – this is mostly true, but there's more...

To understand precisely why the original query is slow, it's worth looking at it's bottlenecks in different scenarios. From further benchmarking, we find that this query,

region_records
  .where.not(registration_facility: current_facility)
  .updated_on_server_since(other_facilities_processed_since, other_facilities_limit)

is actually blazing fast for a large number of region_records (or a large number of syncable patients) but extremely slow when that number is very small. This is quite unintuitive behavior, but looking at the EXPLAIN and the query, it's clear that the primary limiting factor is the LIMIT.

Effectively, the probability of finding the first 1000 BPs in 30k patients is higher than finding the first 1000 BPs in 2k patients. In other words, finding the first 1000 rows matching X becomes slower as the condition X becomes more restrictive.

any

If we look at the slower EXPLAIN, we notice that the most amount of time spent is in the loops. We're looping over each blood pressure and checking if there's a BP for the matching patient. This is very inefficient generally as explained earlier.

Changing this query to an ANY(array()) reaps benefits. EXPLAIN. Notice how the loops have dramatically reduced. What we've done is effectively do an in-built ANY check (on an array) rather than iterating over each BP.

This isn't as fast as the fastest regular query, but it's overall better normalized performance in different cases[1]

However, if we take a step back, we aren't really optimizing for mass resyncing a FacilityGroup, all we really need is:

  • Normal FG syncs finish in reasonable times
  • Bulk block re-syncs happen fast

The query itself is unnecessarily complicated, since it tries to make two different where and where.not clauses,

BloodPressure
  .with_discarded
  .where(patient: Patient.syncable_to_region(region))
  .where.not(patient: prioritized_patients)
  .updated_on_server_since(current_facility_processed_since, limit)

can be rewritten as,

BloodPressure
  .with_discarded
  .where(patient: region.syncable_patients))
  .where.not(patient: prioritized_patients)
  .updated_on_server_since(current_facility_processed_since, limit)

can be rewritten as,

BloodPressure
  .with_discarded
  .where(patient: region.syncable_patients.where.not(facility: current_facility))
  .updated_on_server_since(current_facility_processed_since, limit)

which is both, fewer indirections and faster since we avoid an entire separate where.not clause.

memoization

We end up calling records_to_sync / current_facility_records / other_facility_records multiple times for a single request. Rails helps with query caching, but a fair few of these queries have added modifiers like count which don't really hit the Rails query cache per request. Memoizing these queries gives us a ton of gains. It also has a nice side-effect which is that the benchmarking is on an even keel between different queries and all the processing noise is eliminated.

what indexes are we using now?

With the final bunch of queries, we tend to use only:

  • updated_at index on all the models except Patient
  • Compound index of id and updated_at on Patient

My recommendation would be to continue to keep the existing index infrastructure and monitoring the index usage periodically and remove the non-essentials.

does this work?

Before and after graphs on perf for both Block and FG,

master code with 5% of users resyncing FacilityGroup
Screenshot 2020-12-30 at 1 25 19 PM

sync-perf-fixes code with 5% of users resyncing Block

Screenshot 2020-12-30 at 1 25 39 PM

master code with DB CPU utilization on 5% of users resyncing FacilityGroup
Screenshot 2020-12-30 at 1 31 01 PM

sync-perf-fixes code with DB CPU utilization on 5% of users resyncing Block
Screenshot 2020-12-30 at 1 31 10 PM

master code with 1 instance running (8 vCPUs)
Screenshot 2020-12-30 at 1 34 43 PM

sync-perf-fixes code with 1 instance running (8 vCPUs)
Screenshot 2020-12-30 at 1 34 15 PM

I'm confident that these changes should work for a 5% rollout for block-sync and existing regular FG syncs as they happen.

__references

[1] The cases we've been testing are: Fetch 1000 BPs and Patients from beginning, Fetch 1000 BPs and Patients from different time segments since 2018 to 2020.

Error for new facilities? ActionView::Template::Error: undefined method `attributes' for nil:NilClass

Sentry Issue: SIMPLE-SERVER-1KT

NoMethodError: undefined method `attributes' for nil:NilClass
  app/components/reports/monthly_progress_component.rb:46:in `monthly_count'
    monthly_counts[period].attributes[dimension.field]
  app/components/reports/monthly_progress_component.html.erb:12:in `block (2 levels) in call'
    <%= number_or_zero_with_delimiter monthly_count(period) %>
  app/components/reports/monthly_progress_component.html.erb:8:in `reverse_each'
    <% range.reverse_each do |period| %>
  app/components/reports/monthly_progress_component.html.erb:8:in `block in call'
    <% range.reverse_each do |period| %>
  app/components/reports/monthly_progress_component.rb:33:in `table'
    tag.table(options, &block)
...
(145 additional frame(s) were not displayed)

ActionView::Template::Error: undefined method `attributes' for nil:NilClass

Sync patient data from user to server

As a nurse, I want the patient records on device to be sent in batches to the backend, so that the central database is updated, and the data is safe.

Patient data model

ID - UUID [to discuss]
Full Name - String
Age when created - Integer
Date of Birth - Date
Gender (male / female / transgender) - Enum/String
Status (active, dead, migrated, unresponsive, other [define other]) - Enum/String

Address data model

id – uuid
street address - string
colony - string
village - string
district - string
state - string
country - string
pin - string

Phone number data model

number - string
type -string
active - boolean

Relationships

Patient belongs to an address
Patient has and belongs to many phone numbers

Production DB schema (as of 31 Aug, 2020)

Dumped here for sanity checking our production schema against the local one for any missing features or issues.

# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2020_08_11_135315) do

  # These are extensions that must be enabled in order to support this database
  enable_extension "pgcrypto"
  enable_extension "plpgsql"

  create_table "accesses", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.uuid "user_id", null: false
    t.string "resource_type"
    t.uuid "resource_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "deleted_at"
    t.index ["resource_type", "resource_id"], name: "index_accesses_on_resource_type_and_resource_id"
    t.index ["user_id", "resource_id", "resource_type"], name: "index_accesses_on_user_id_and_resource_id_and_resource_type", unique: true
    t.index ["user_id"], name: "index_accesses_on_user_id"
  end

  create_table "addresses", id: :uuid, default: nil, force: :cascade do |t|
    t.string "street_address"
    t.string "village_or_colony"
    t.string "district"
    t.string "state"
    t.string "country"
    t.string "pin"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "device_created_at", null: false
    t.datetime "device_updated_at", null: false
    t.datetime "deleted_at"
    t.string "zone"
    t.index ["deleted_at"], name: "index_addresses_on_deleted_at"
    t.index ["zone"], name: "index_addresses_on_zone"
  end

  create_table "appointments", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.uuid "patient_id", null: false
    t.uuid "facility_id", null: false
    t.date "scheduled_date", null: false
    t.string "status"
    t.string "cancel_reason"
    t.datetime "device_created_at", null: false
    t.datetime "device_updated_at", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.date "remind_on"
    t.boolean "agreed_to_visit"
    t.datetime "deleted_at"
    t.string "appointment_type", null: false
    t.uuid "user_id"
    t.uuid "creation_facility_id"
    t.index ["appointment_type"], name: "index_appointments_on_appointment_type"
    t.index ["deleted_at"], name: "index_appointments_on_deleted_at"
    t.index ["facility_id"], name: "index_appointments_on_facility_id"
    t.index ["patient_id", "scheduled_date"], name: "index_appointments_on_patient_id_and_scheduled_date", order: { scheduled_date: :desc }
    t.index ["patient_id"], name: "index_appointments_on_patient_id"
    t.index ["updated_at"], name: "index_appointments_on_updated_at"
    t.index ["user_id"], name: "index_appointments_on_user_id"
  end

  create_table "blood_pressures", id: :uuid, default: nil, force: :cascade do |t|
    t.integer "systolic", null: false
    t.integer "diastolic", null: false
    t.uuid "patient_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "device_created_at", null: false
    t.datetime "device_updated_at", null: false
    t.uuid "facility_id", null: false
    t.uuid "user_id"
    t.datetime "deleted_at"
    t.datetime "recorded_at"
    t.index ["deleted_at"], name: "index_blood_pressures_on_deleted_at"
    t.index ["patient_id", "recorded_at"], name: "index_blood_pressures_on_patient_id_and_recorded_at", order: { recorded_at: :desc }
    t.index ["patient_id"], name: "index_blood_pressures_on_patient_id"
    t.index ["recorded_at"], name: "index_blood_pressures_on_recorded_at"
    t.index ["updated_at"], name: "index_blood_pressures_on_updated_at"
    t.index ["user_id"], name: "index_blood_pressures_on_user_id"
  end

  create_table "blood_sugars", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "blood_sugar_type", null: false
    t.decimal "blood_sugar_value", null: false
    t.uuid "patient_id", null: false
    t.uuid "user_id", null: false
    t.uuid "facility_id", null: false
    t.datetime "device_created_at", null: false
    t.datetime "device_updated_at", null: false
    t.datetime "deleted_at"
    t.datetime "recorded_at", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["blood_sugar_type"], name: "index_blood_sugars_on_blood_sugar_type"
    t.index ["blood_sugar_value"], name: "index_blood_sugars_on_blood_sugar_value"
    t.index ["facility_id"], name: "index_blood_sugars_on_facility_id"
    t.index ["patient_id"], name: "index_blood_sugars_on_patient_id"
    t.index ["updated_at"], name: "index_blood_sugars_on_updated_at"
    t.index ["user_id"], name: "index_blood_sugars_on_user_id"
  end

  create_table "call_logs", force: :cascade do |t|
    t.string "session_id"
    t.string "result"
    t.integer "duration"
    t.string "callee_phone_number", null: false
    t.datetime "start_time"
    t.datetime "end_time"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "caller_phone_number", null: false
    t.datetime "deleted_at"
    t.index ["deleted_at"], name: "index_call_logs_on_deleted_at"
  end

  create_table "communications", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.uuid "appointment_id", null: false
    t.uuid "user_id"
    t.string "communication_type"
    t.datetime "device_created_at", null: false
    t.datetime "device_updated_at", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "deleted_at"
    t.string "detailable_type"
    t.bigint "detailable_id"
    t.index ["appointment_id"], name: "index_communications_on_appointment_id"
    t.index ["deleted_at"], name: "index_communications_on_deleted_at"
    t.index ["detailable_type", "detailable_id"], name: "index_communications_on_detailable_type_and_detailable_id"
    t.index ["user_id"], name: "index_communications_on_user_id"
  end

  create_table "data_migrations", id: false, force: :cascade do |t|
    t.string "version", null: false
    t.index ["version"], name: "unique_data_migrations", unique: true
  end

  create_table "email_authentications", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "email", default: "", null: false
    t.string "encrypted_password", default: "", null: false
    t.string "reset_password_token"
    t.datetime "reset_password_sent_at"
    t.datetime "remember_created_at"
    t.integer "sign_in_count", default: 0, null: false
    t.datetime "current_sign_in_at"
    t.datetime "last_sign_in_at"
    t.inet "current_sign_in_ip"
    t.inet "last_sign_in_ip"
    t.integer "failed_attempts", default: 0, null: false
    t.string "unlock_token"
    t.datetime "locked_at"
    t.string "invitation_token"
    t.datetime "invitation_created_at"
    t.datetime "invitation_sent_at"
    t.datetime "invitation_accepted_at"
    t.integer "invitation_limit"
    t.uuid "invited_by_id"
    t.string "invited_by_type"
    t.integer "invitations_count", default: 0
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "deleted_at"
    t.index "to_tsvector('simple'::regconfig, COALESCE((email)::text, ''::text))", name: "index_gin_email_authentications_on_email", using: :gin
    t.index ["deleted_at"], name: "index_email_authentications_on_deleted_at"
    t.index ["email"], name: "index_email_authentications_on_email", unique: true
    t.index ["invitation_token"], name: "index_email_authentications_on_invitation_token", unique: true
    t.index ["invitations_count"], name: "index_email_authentications_on_invitations_count"
    t.index ["invited_by_id"], name: "index_email_authentications_on_invited_by_id"
    t.index ["invited_by_type", "invited_by_id"], name: "index_email_authentications_invited_by"
    t.index ["reset_password_token"], name: "index_email_authentications_on_reset_password_token", unique: true
    t.index ["unlock_token"], name: "index_email_authentications_on_unlock_token", unique: true
  end

  create_table "encounters", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.uuid "facility_id", null: false
    t.uuid "patient_id", null: false
    t.date "encountered_on", null: false
    t.integer "timezone_offset", null: false
    t.text "notes"
    t.jsonb "metadata"
    t.datetime "device_created_at", null: false
    t.datetime "device_updated_at", null: false
    t.datetime "deleted_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["deleted_at"], name: "index_encounters_on_deleted_at"
    t.index ["facility_id"], name: "index_encounters_on_facility_id"
    t.index ["patient_id"], name: "index_encounters_on_patient_id"
  end

  create_table "exotel_phone_number_details", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.uuid "patient_phone_number_id", null: false
    t.string "whitelist_status"
    t.datetime "whitelist_requested_at"
    t.datetime "whitelist_status_valid_until"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "deleted_at"
    t.index ["deleted_at"], name: "index_exotel_phone_number_details_on_deleted_at"
    t.index ["patient_phone_number_id"], name: "index_exotel_phone_number_details_on_patient_phone_number_id"
    t.index ["patient_phone_number_id"], name: "index_unique_exotel_phone_number_details_on_phone_number_id", unique: true
    t.index ["whitelist_status"], name: "index_exotel_phone_number_details_on_whitelist_status"
  end

  create_table "facilities", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "name"
    t.string "street_address"
    t.string "village_or_colony"
    t.string "district"
    t.string "state"
    t.string "country"
    t.string "pin"
    t.string "facility_type"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.float "latitude"
    t.float "longitude"
    t.datetime "deleted_at"
    t.uuid "facility_group_id"
    t.string "slug"
    t.string "zone"
    t.boolean "enable_diabetes_management", default: false, null: false
    t.string "facility_size"
    t.integer "monthly_estimated_opd_load"
    t.boolean "enable_teleconsultation", default: false, null: false
    t.string "teleconsultation_phone_number"
    t.string "teleconsultation_isd_code"
    t.jsonb "teleconsultation_phone_numbers", default: [], null: false
    t.index "to_tsvector('simple'::regconfig, COALESCE((name)::text, ''::text))", name: "index_gin_facilities_on_name", using: :gin
    t.index "to_tsvector('simple'::regconfig, COALESCE((slug)::text, ''::text))", name: "index_gin_facilities_on_slug", using: :gin
    t.index ["deleted_at"], name: "index_facilities_on_deleted_at"
    t.index ["enable_diabetes_management"], name: "index_facilities_on_enable_diabetes_management"
    t.index ["facility_group_id"], name: "index_facilities_on_facility_group_id"
    t.index ["slug"], name: "index_facilities_on_slug", unique: true
  end

  create_table "facility_groups", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "name"
    t.text "description"
    t.uuid "organization_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "deleted_at"
    t.uuid "protocol_id"
    t.string "slug"
    t.index ["deleted_at"], name: "index_facility_groups_on_deleted_at"
    t.index ["organization_id"], name: "index_facility_groups_on_organization_id"
    t.index ["protocol_id"], name: "index_facility_groups_on_protocol_id"
    t.index ["slug"], name: "index_facility_groups_on_slug", unique: true
  end

  create_table "flipper_features", force: :cascade do |t|
    t.string "key", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["key"], name: "index_flipper_features_on_key", unique: true
  end

  create_table "flipper_gates", force: :cascade do |t|
    t.string "feature_key", null: false
    t.string "key", null: false
    t.string "value"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["feature_key", "key", "value"], name: "index_flipper_gates_on_feature_key_and_key_and_value", unique: true
  end

  create_table "medical_histories", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.uuid "patient_id", null: false
    t.boolean "prior_heart_attack_boolean"
    t.boolean "prior_stroke_boolean"
    t.boolean "chronic_kidney_disease_boolean"
    t.boolean "receiving_treatment_for_hypertension_boolean"
    t.boolean "diabetes_boolean"
    t.datetime "device_created_at", null: false
    t.datetime "device_updated_at", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.boolean "diagnosed_with_hypertension_boolean"
    t.text "prior_heart_attack"
    t.text "prior_stroke"
    t.text "chronic_kidney_disease"
    t.text "receiving_treatment_for_hypertension"
    t.text "diabetes"
    t.text "diagnosed_with_hypertension"
    t.datetime "deleted_at"
    t.uuid "user_id"
    t.text "hypertension"
    t.index ["deleted_at"], name: "index_medical_histories_on_deleted_at"
    t.index ["patient_id"], name: "index_medical_histories_on_patient_id"
    t.index ["user_id"], name: "index_medical_histories_on_user_id"
  end

  create_table "observations", force: :cascade do |t|
    t.uuid "encounter_id", null: false
    t.uuid "user_id", null: false
    t.string "observable_type"
    t.uuid "observable_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "deleted_at"
    t.index ["deleted_at"], name: "index_observations_on_deleted_at"
    t.index ["encounter_id"], name: "index_observations_on_encounter_id"
    t.index ["observable_type", "observable_id"], name: "idx_observations_on_observable_type_and_id", unique: true
    t.index ["user_id"], name: "index_observations_on_user_id"
  end

  create_table "organizations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "name", null: false
    t.text "description"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "deleted_at"
    t.string "slug"
    t.index ["deleted_at"], name: "index_organizations_on_deleted_at"
    t.index ["slug"], name: "index_organizations_on_slug", unique: true
  end

  create_table "passport_authentications", force: :cascade do |t|
    t.string "access_token", null: false
    t.string "otp", null: false
    t.datetime "otp_expires_at", null: false
    t.uuid "patient_business_identifier_id", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "patient_business_identifiers", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "identifier", null: false
    t.string "identifier_type", null: false
    t.uuid "patient_id", null: false
    t.string "metadata_version"
    t.json "metadata"
    t.datetime "device_created_at", null: false
    t.datetime "device_updated_at", null: false
    t.datetime "deleted_at"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.index ["deleted_at"], name: "index_patient_business_identifiers_on_deleted_at"
    t.index ["patient_id"], name: "index_patient_business_identifiers_on_patient_id"
  end

  create_table "patient_phone_numbers", id: :uuid, default: nil, force: :cascade do |t|
    t.string "number"
    t.string "phone_type"
    t.boolean "active"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.uuid "patient_id"
    t.datetime "device_created_at", null: false
    t.datetime "device_updated_at", null: false
    t.datetime "deleted_at"
    t.boolean "dnd_status", default: true, null: false
    t.index ["deleted_at"], name: "index_patient_phone_numbers_on_deleted_at"
    t.index ["dnd_status"], name: "index_patient_phone_numbers_on_dnd_status"
    t.index ["patient_id"], name: "index_patient_phone_numbers_on_patient_id"
  end

  create_table "patients", id: :uuid, default: nil, force: :cascade do |t|
    t.string "full_name"
    t.integer "age"
    t.string "gender"
    t.date "date_of_birth"
    t.string "status"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.uuid "address_id"
    t.datetime "age_updated_at"
    t.datetime "device_created_at", null: false
    t.datetime "device_updated_at", null: false
    t.boolean "test_data", default: false, null: false
    t.uuid "registration_facility_id"
    t.uuid "registration_user_id"
    t.datetime "deleted_at"
    t.boolean "contacted_by_counsellor", default: false
    t.string "could_not_contact_reason"
    t.datetime "recorded_at"
    t.string "reminder_consent", default: "denied", null: false
    t.uuid "deleted_by_user_id"
    t.string "deleted_reason"
    t.uuid "assigned_facility_id"
    t.index ["assigned_facility_id"], name: "index_patients_on_assigned_facility_id"
    t.index ["deleted_at"], name: "index_patients_on_deleted_at"
    t.index ["recorded_at"], name: "index_patients_on_recorded_at"
    t.index ["registration_facility_id"], name: "index_patients_on_registration_facility_id"
    t.index ["registration_user_id"], name: "index_patients_on_registration_user_id"
    t.index ["reminder_consent"], name: "index_patients_on_reminder_consent"
    t.index ["updated_at"], name: "index_patients_on_updated_at"
  end

  create_table "phone_number_authentications", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "phone_number", null: false
    t.string "password_digest", null: false
    t.string "otp", null: false
    t.datetime "otp_expires_at", null: false
    t.datetime "logged_in_at"
    t.string "access_token", null: false
    t.uuid "registration_facility_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "deleted_at"
    t.integer "failed_attempts", default: 0, null: false
    t.datetime "locked_at"
    t.index "to_tsvector('simple'::regconfig, COALESCE((phone_number)::text, ''::text))", name: "index_gin_phone_number_authentications_on_phone_number", using: :gin
    t.index ["deleted_at"], name: "index_phone_number_authentications_on_deleted_at"
  end

  create_table "prescription_drugs", id: :uuid, default: nil, force: :cascade do |t|
    t.string "name", null: false
    t.string "rxnorm_code"
    t.string "dosage"
    t.datetime "device_created_at", null: false
    t.datetime "device_updated_at", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.uuid "patient_id", null: false
    t.uuid "facility_id", null: false
    t.boolean "is_protocol_drug", null: false
    t.boolean "is_deleted", null: false
    t.datetime "deleted_at"
    t.uuid "user_id"
    t.index ["deleted_at"], name: "index_prescription_drugs_on_deleted_at"
    t.index ["patient_id"], name: "index_prescription_drugs_on_patient_id"
    t.index ["user_id"], name: "index_prescription_drugs_on_user_id"
  end

  create_table "protocol_drugs", id: :uuid, default: nil, force: :cascade do |t|
    t.string "name", null: false
    t.string "dosage", null: false
    t.string "rxnorm_code"
    t.uuid "protocol_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "deleted_at"
    t.index ["deleted_at"], name: "index_protocol_drugs_on_deleted_at"
  end

  create_table "protocols", id: :uuid, default: nil, force: :cascade do |t|
    t.string "name", null: false
    t.integer "follow_up_days"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "deleted_at"
    t.index ["deleted_at"], name: "index_protocols_on_deleted_at"
  end

  create_table "twilio_sms_delivery_details", force: :cascade do |t|
    t.string "session_id"
    t.string "result"
    t.string "callee_phone_number", null: false
    t.datetime "delivered_on"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "deleted_at"
    t.index ["deleted_at"], name: "index_twilio_sms_delivery_details_on_deleted_at"
  end

  create_table "user_authentications", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.uuid "user_id"
    t.string "authenticatable_type"
    t.uuid "authenticatable_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "deleted_at"
    t.index ["user_id", "authenticatable_type", "authenticatable_id"], name: "user_authentications_master_users_authenticatable_uniq_index", unique: true
    t.index ["user_id"], name: "index_user_authentications_on_user_id"
  end

  create_table "user_permissions", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.uuid "user_id", null: false
    t.string "permission_slug"
    t.string "resource_type"
    t.uuid "resource_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "deleted_at"
    t.index ["resource_type", "resource_id"], name: "index_user_permissions_on_resource_type_and_resource_id"
  end

  create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
    t.string "full_name"
    t.string "sync_approval_status", null: false
    t.string "sync_approval_status_reason"
    t.datetime "device_updated_at", null: false
    t.datetime "device_created_at", null: false
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.datetime "deleted_at"
    t.string "role"
    t.uuid "organization_id"
    t.string "access_level"
    t.index "to_tsvector('simple'::regconfig, COALESCE((full_name)::text, ''::text))", name: "index_gin_users_on_full_name", using: :gin
    t.index ["access_level"], name: "index_users_on_access_level"
    t.index ["deleted_at"], name: "index_users_on_deleted_at"
    t.index ["organization_id"], name: "index_users_on_organization_id"
  end

  add_foreign_key "accesses", "users"
  add_foreign_key "appointments", "facilities"
  add_foreign_key "blood_sugars", "facilities"
  add_foreign_key "blood_sugars", "users"
  add_foreign_key "encounters", "facilities"
  add_foreign_key "exotel_phone_number_details", "patient_phone_numbers"
  add_foreign_key "facilities", "facility_groups"
  add_foreign_key "facility_groups", "organizations"
  add_foreign_key "observations", "encounters"
  add_foreign_key "observations", "users"
  add_foreign_key "patient_phone_numbers", "patients"
  add_foreign_key "patients", "addresses"
  add_foreign_key "patients", "facilities", column: "assigned_facility_id"
  add_foreign_key "patients", "facilities", column: "registration_facility_id"
  add_foreign_key "protocol_drugs", "protocols"

  create_view "bp_drugs_views", sql_definition: <<-SQL
      SELECT bp.id AS bp_id,
      bp.systolic,
      bp.diastolic,
      bp.created_at,
      bp.updated_at,
      bp.device_created_at AS visit_date,
      bp.facility_id,
      bp.user_id,
      p.id AS patient_id,
      p.full_name,
      p.age,
      p.gender,
      p.date_of_birth,
      p.status AS patient_status,
      p.address_id,
      p.device_created_at AS registration_date,
      a.street_address,
      a.village_or_colony,
      a.district,
      a.state,
      a.country,
      a.pin,
      f.name AS facility_name,
      f.facility_type,
      f.latitude,
      f.longitude,
      u.full_name AS user_name,
      pn.number AS phone_number,
      pn.phone_type,
      pd.id AS drug_id,
      pd.name AS drug_name,
      pd.rxnorm_code,
      pd.dosage,
      pd.is_protocol_drug,
      pd.is_deleted
     FROM ((((((patients p
       JOIN blood_pressures bp ON ((p.id = bp.patient_id)))
       JOIN facilities f ON ((f.id = bp.facility_id)))
       JOIN addresses a ON ((p.address_id = a.id)))
       JOIN users u ON ((u.id = bp.user_id)))
       LEFT JOIN patient_phone_numbers pn ON ((pn.patient_id = p.id)))
       LEFT JOIN prescription_drugs pd ON (((bp.patient_id = pd.patient_id) AND (date_trunc('day'::text, bp.device_created_at) = date_trunc('day'::text, pd.device_created_at)))));
  SQL
  create_view "bp_views", sql_definition: <<-SQL
      SELECT bp.id AS bp_id,
      bp.systolic,
      bp.diastolic,
      bp.created_at,
      bp.updated_at,
      bp.device_created_at AS visit_date,
      bp.facility_id,
      bp.user_id,
      p.id AS patient_id,
      p.full_name,
      p.age,
      p.gender,
      p.date_of_birth,
      p.status AS patient_status,
      p.address_id,
      p.device_created_at AS registration_date,
      a.street_address,
      a.village_or_colony,
      a.district,
      a.state,
      a.country,
      a.pin,
      f.name AS facility_name,
      f.facility_type,
      f.latitude,
      f.longitude,
      u.full_name AS user_name,
      pn.number AS phone_number,
      pn.phone_type
     FROM (((((patients p
       JOIN blood_pressures bp ON ((p.id = bp.patient_id)))
       JOIN facilities f ON ((f.id = bp.facility_id)))
       JOIN addresses a ON ((p.address_id = a.id)))
       JOIN users u ON ((u.id = bp.user_id)))
       LEFT JOIN patient_phone_numbers pn ON ((pn.patient_id = p.id)));
  SQL
  create_view "follow_up_views", sql_definition: <<-SQL
      SELECT ap.id,
      ap.patient_id,
      ap.facility_id,
      ap.scheduled_date,
      ap.status,
      ap.cancel_reason,
      ap.device_created_at,
      ap.device_updated_at,
      ap.created_at,
      ap.updated_at,
      ap.remind_on,
      ap.agreed_to_visit,
      date_part('day'::text, (lead(ap.device_created_at, 1) OVER (PARTITION BY ap.patient_id ORDER BY ap.scheduled_date) - (ap.scheduled_date)::timestamp without time zone)) AS follow_up_delta,
      f.name AS facility_name,
      f.facility_type,
      f.latitude,
      f.longitude
     FROM (appointments ap
       JOIN facilities f ON ((ap.facility_id = f.id)));
  SQL
  create_view "overdue_views", sql_definition: <<-SQL
      SELECT ap.id AS appointment_id,
      ap.facility_id,
      ap.scheduled_date,
      ap.status AS appointment_status,
      ap.cancel_reason,
      ap.device_created_at,
      ap.device_updated_at,
      ap.created_at,
      ap.updated_at,
      ap.remind_on,
      ap.agreed_to_visit,
      p.id AS patient_id,
      p.full_name,
      p.age,
      p.gender,
      p.date_of_birth,
      p.status AS patient_status,
      p.address_id,
      p.device_created_at AS registration_date,
      a.street_address,
      a.village_or_colony,
      a.district,
      a.state,
      a.country,
      a.pin,
      f.name AS facility_name,
      f.facility_type,
      f.latitude,
      f.longitude
     FROM ((((appointments ap
       JOIN patients p ON ((p.id = ap.patient_id)))
       JOIN facilities f ON ((f.id = ap.facility_id)))
       JOIN addresses a ON ((p.address_id = a.id)))
       LEFT JOIN patient_phone_numbers pn ON ((pn.patient_id = p.id)));
  SQL
  create_view "patient_first_bp_views", sql_definition: <<-SQL
      SELECT bp.id AS bp_id,
      bp.systolic,
      bp.diastolic,
      bp.created_at,
      bp.updated_at,
      bp.device_created_at AS visit_date,
      bp.facility_id,
      bp.user_id,
      p.id AS patient_id,
      p.full_name,
      p.age,
      p.gender,
      p.date_of_birth,
      p.status AS patient_status,
      p.address_id,
      p.device_created_at AS registration_date,
      a.street_address,
      a.village_or_colony,
      a.district,
      a.state,
      a.country,
      a.pin,
      f.name AS facility_name,
      f.facility_type,
      f.latitude,
      f.longitude,
      u.full_name AS user_name,
      pn.number AS phone_number,
      pn.phone_type
     FROM (((((patients p
       LEFT JOIN blood_pressures bp ON (((p.id = bp.patient_id) AND (date_trunc('day'::text, p.device_created_at) = date_trunc('day'::text, bp.device_created_at)))))
       JOIN facilities f ON ((f.id = bp.facility_id)))
       JOIN addresses a ON ((p.address_id = a.id)))
       JOIN users u ON ((u.id = bp.user_id)))
       LEFT JOIN patient_phone_numbers pn ON ((pn.patient_id = p.id)));
  SQL
  create_view "patients_blood_pressures_facilities", sql_definition: <<-SQL
      SELECT patients.id AS p_id,
      patients.age AS p_age,
      patients.gender AS p_gender,
      blood_pressures.id,
      blood_pressures.systolic,
      blood_pressures.diastolic,
      blood_pressures.patient_id,
      blood_pressures.created_at,
      blood_pressures.updated_at,
      blood_pressures.device_created_at,
      blood_pressures.device_updated_at,
      blood_pressures.facility_id,
      blood_pressures.user_id,
      blood_pressures.deleted_at,
      facilities.name,
      facilities.district,
      facilities.state,
      facilities.facility_type,
      users.full_name,
      users.sync_approval_status
     FROM patients,
      blood_pressures,
      facilities,
      users
    WHERE ((blood_pressures.patient_id = patients.id) AND (blood_pressures.facility_id = facilities.id) AND (blood_pressures.user_id = users.id));
  SQL
  create_view "patient_registrations_per_day_per_facilities", materialized: true, sql_definition: <<-SQL
      SELECT count(patients.id) AS registration_count,
      patients.registration_facility_id AS facility_id,
      timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, facilities.deleted_at)) AS deleted_at,
      (date_part('doy'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, patients.recorded_at))))::text AS day,
      (date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, patients.recorded_at))))::text AS month,
      (date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, patients.recorded_at))))::text AS quarter,
      (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, patients.recorded_at))))::text AS year
     FROM ((patients
       JOIN facilities ON ((patients.registration_facility_id = facilities.id)))
       JOIN medical_histories ON (((patients.id = medical_histories.patient_id) AND (medical_histories.hypertension = 'yes'::text))))
    WHERE (patients.deleted_at IS NULL)
    GROUP BY (date_part('doy'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, patients.recorded_at))))::text, (date_part('month'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, patients.recorded_at))))::text, (date_part('quarter'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, patients.recorded_at))))::text, (date_part('year'::text, timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, patients.recorded_at))))::text, patients.registration_facility_id, facilities.deleted_at;
  SQL
  add_index "patient_registrations_per_day_per_facilities", ["facility_id", "day", "year"], name: "index_patient_registrations_per_day_per_facilities", unique: true

  create_view "blood_pressures_per_facility_per_days", materialized: true, sql_definition: <<-SQL
      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 (blood_pressures
               JOIN 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 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;
  SQL
  add_index "blood_pressures_per_facility_per_days", ["facility_id", "day", "year"], name: "index_blood_pressures_per_facility_per_days", unique: true

  create_view "patient_summaries", sql_definition: <<-SQL
      SELECT p.recorded_at,
      concat(date_part('year'::text, p.recorded_at), ' Q', date_part('quarter'::text, p.recorded_at)) AS registration_quarter,
      p.full_name,
          CASE
              WHEN (p.date_of_birth IS NOT NULL) THEN date_part('year'::text, age((p.date_of_birth)::timestamp with time zone))
              ELSE ((p.age)::double precision + date_part('years'::text, age(now(), (p.age_updated_at)::timestamp with time zone)))
          END 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.state,
      reg_facility.name AS registration_facility_name,
      reg_facility.facility_type AS registration_facility_type,
      reg_facility.district AS registration_district,
      reg_facility.state AS registration_state,
      latest_blood_pressure.systolic AS latest_blood_pressure_systolic,
      latest_blood_pressure.diastolic AS latest_blood_pressure_diastolic,
      latest_blood_pressure.recorded_at AS latest_blood_pressure_recorded_at,
      concat(date_part('year'::text, latest_blood_pressure.recorded_at), ' Q', date_part('quarter'::text, latest_blood_pressure.recorded_at)) AS latest_blood_pressure_quarter,
      latest_blood_pressure_facility.name AS latest_blood_pressure_facility_name,
      latest_blood_pressure_facility.facility_type AS latest_blood_pressure_facility_type,
      latest_blood_pressure_facility.district AS latest_blood_pressure_district,
      latest_blood_pressure_facility.state AS latest_blood_pressure_state,
      latest_blood_sugar.blood_sugar_type AS latest_blood_sugar_type,
      latest_blood_sugar.blood_sugar_value AS latest_blood_sugar_value,
      latest_blood_sugar.recorded_at AS latest_blood_sugar_recorded_at,
      concat(date_part('year'::text, latest_blood_sugar.recorded_at), ' Q', date_part('quarter'::text, latest_blood_sugar.recorded_at)) AS latest_blood_sugar_quarter,
      latest_blood_sugar_facility.name AS latest_blood_sugar_facility_name,
      latest_blood_sugar_facility.facility_type AS latest_blood_sugar_facility_type,
      latest_blood_sugar_facility.district AS latest_blood_sugar_district,
      latest_blood_sugar_facility.state AS latest_blood_sugar_state,
      GREATEST((0)::double precision, date_part('day'::text, (now() - (next_appointment.scheduled_date)::timestamp with time zone))) AS days_overdue,
      next_appointment.id AS next_appointment_id,
      next_appointment.scheduled_date AS next_appointment_scheduled_date,
      next_appointment.status AS next_appointment_status,
      next_appointment.remind_on AS next_appointment_remind_on,
      next_appointment_facility.id AS next_appointment_facility_id,
      next_appointment_facility.name AS next_appointment_facility_name,
      next_appointment_facility.facility_type AS next_appointment_facility_type,
      next_appointment_facility.district AS next_appointment_district,
      next_appointment_facility.state AS next_appointment_state,
          CASE
              WHEN (next_appointment.scheduled_date IS NULL) THEN 0
              WHEN (next_appointment.scheduled_date > date_trunc('day'::text, (now() - '30 days'::interval))) THEN 0
              WHEN ((latest_blood_pressure.systolic >= 180) OR (latest_blood_pressure.diastolic >= 110)) THEN 1
              WHEN (((mh.prior_heart_attack = 'yes'::text) OR (mh.prior_stroke = 'yes'::text)) AND ((latest_blood_pressure.systolic >= 140) OR (latest_blood_pressure.diastolic >= 90))) THEN 1
              WHEN ((((latest_blood_sugar.blood_sugar_type)::text = 'random'::text) AND (latest_blood_sugar.blood_sugar_value >= (300)::numeric)) OR (((latest_blood_sugar.blood_sugar_type)::text = 'post_prandial'::text) AND (latest_blood_sugar.blood_sugar_value >= (300)::numeric)) OR (((latest_blood_sugar.blood_sugar_type)::text = 'fasting'::text) AND (latest_blood_sugar.blood_sugar_value >= (200)::numeric)) OR (((latest_blood_sugar.blood_sugar_type)::text = 'hba1c'::text) AND (latest_blood_sugar.blood_sugar_value >= 9.0))) THEN 1
              ELSE 0
          END AS risk_level,
      latest_bp_passport.identifier AS latest_bp_passport,
      p.id
     FROM (((((((((((patients p
       LEFT JOIN addresses ON ((addresses.id = p.address_id)))
       LEFT JOIN facilities reg_facility ON ((reg_facility.id = p.registration_facility_id)))
       LEFT JOIN medical_histories mh ON ((mh.patient_id = p.id)))
       LEFT JOIN ( 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 patient_phone_numbers
            ORDER BY patient_phone_numbers.patient_id, patient_phone_numbers.device_created_at DESC) latest_phone_number ON ((latest_phone_number.patient_id = p.id)))
       LEFT JOIN ( SELECT DISTINCT ON (blood_pressures.patient_id) blood_pressures.id,
              blood_pressures.systolic,
              blood_pressures.diastolic,
              blood_pressures.patient_id,
              blood_pressures.created_at,
              blood_pressures.updated_at,
              blood_pressures.device_created_at,
              blood_pressures.device_updated_at,
              blood_pressures.facility_id,
              blood_pressures.user_id,
              blood_pressures.deleted_at,
              blood_pressures.recorded_at
             FROM blood_pressures
            ORDER BY blood_pressures.patient_id, blood_pressures.recorded_at DESC) latest_blood_pressure ON ((latest_blood_pressure.patient_id = p.id)))
       LEFT JOIN facilities latest_blood_pressure_facility ON ((latest_blood_pressure_facility.id = latest_blood_pressure.facility_id)))
       LEFT JOIN ( SELECT DISTINCT ON (blood_sugars.patient_id) blood_sugars.id,
              blood_sugars.blood_sugar_type,
              blood_sugars.blood_sugar_value,
              blood_sugars.patient_id,
              blood_sugars.user_id,
              blood_sugars.facility_id,
              blood_sugars.device_created_at,
              blood_sugars.device_updated_at,
              blood_sugars.deleted_at,
              blood_sugars.recorded_at,
              blood_sugars.created_at,
              blood_sugars.updated_at
             FROM blood_sugars
            ORDER BY blood_sugars.patient_id, blood_sugars.recorded_at DESC) latest_blood_sugar ON ((latest_blood_sugar.patient_id = p.id)))
       LEFT JOIN facilities latest_blood_sugar_facility ON ((latest_blood_sugar_facility.id = latest_blood_sugar.facility_id)))
       LEFT JOIN ( 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 patient_business_identifiers
            WHERE ((patient_business_identifiers.identifier_type)::text = 'simple_bp_passport'::text)
            ORDER BY patient_business_identifiers.patient_id, patient_business_identifiers.device_created_at DESC) latest_bp_passport ON ((latest_bp_passport.patient_id = p.id)))
       LEFT JOIN ( 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
             FROM appointments
            ORDER BY appointments.patient_id, appointments.scheduled_date DESC) next_appointment ON ((next_appointment.patient_id = p.id)))
       LEFT JOIN facilities next_appointment_facility ON ((next_appointment_facility.id = next_appointment.facility_id)));
  SQL
  create_view "latest_blood_pressures_per_patient_per_days", materialized: true, sql_definition: <<-SQL
      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('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.patient_id,
      patients.registration_facility_id,
      patients.assigned_facility_id,
      patients.status AS patient_status,
      blood_pressures.facility_id AS bp_facility_id,
      timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at)) AS bp_recorded_at,
      timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, patients.recorded_at)) AS patient_recorded_at,
      blood_pressures.systolic,
      blood_pressures.diastolic,
      timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.deleted_at)) AS deleted_at,
      medical_histories.hypertension AS medical_history_hypertension,
      (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 ((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)
    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('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;
  SQL
  add_index "latest_blood_pressures_per_patient_per_days", ["bp_id"], name: "index_latest_blood_pressures_per_patient_per_days", unique: true

  create_view "latest_blood_pressures_per_patient_per_months", materialized: true, sql_definition: <<-SQL
      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(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, blood_pressures.recorded_at)) AS bp_recorded_at,
      timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), timezone('utc'::text, patients.recorded_at)) AS patient_recorded_at,
      blood_pressures.systolic,
      blood_pressures.diastolic,
      timezone(( SELECT current_setting('TIMEZONE'::text) AS current_setting), 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 ((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)
    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;
  SQL
  add_index "latest_blood_pressures_per_patient_per_months", ["bp_id"], name: "index_latest_blood_pressures_per_patient_per_months", unique: true

  create_view "latest_blood_pressures_per_patient_per_quarters", materialized: true, sql_definition: <<-SQL
      SELECT DISTINCT ON (latest_blood_pressures_per_patient_per_months.patient_id, latest_blood_pressures_per_patient_per_months.year, latest_blood_pressures_per_patient_per_months.quarter) latest_blood_pressures_per_patient_per_months.bp_id,
      latest_blood_pressures_per_patient_per_months.patient_id,
      latest_blood_pressures_per_patient_per_months.registration_facility_id,
      latest_blood_pressures_per_patient_per_months.assigned_facility_id,
      latest_blood_pressures_per_patient_per_months.patient_status,
      latest_blood_pressures_per_patient_per_months.bp_facility_id,
      latest_blood_pressures_per_patient_per_months.bp_recorded_at,
      latest_blood_pressures_per_patient_per_months.patient_recorded_at,
      latest_blood_pressures_per_patient_per_months.systolic,
      latest_blood_pressures_per_patient_per_months.diastolic,
      latest_blood_pressures_per_patient_per_months.deleted_at,
      latest_blood_pressures_per_patient_per_months.medical_history_hypertension,
      latest_blood_pressures_per_patient_per_months.month,
      latest_blood_pressures_per_patient_per_months.quarter,
      latest_blood_pressures_per_patient_per_months.year
     FROM latest_blood_pressures_per_patient_per_months
    ORDER BY latest_blood_pressures_per_patient_per_months.patient_id, latest_blood_pressures_per_patient_per_months.year, latest_blood_pressures_per_patient_per_months.quarter, latest_blood_pressures_per_patient_per_months.bp_recorded_at DESC, latest_blood_pressures_per_patient_per_months.bp_id;
  SQL
  add_index "latest_blood_pressures_per_patient_per_quarters", ["bp_id"], name: "index_latest_blood_pressures_per_patient_per_quarters", unique: true

  create_view "latest_blood_pressures_per_patients", materialized: true, sql_definition: <<-SQL
      SELECT DISTINCT ON (latest_blood_pressures_per_patient_per_months.patient_id) latest_blood_pressures_per_patient_per_months.bp_id,
      latest_blood_pressures_per_patient_per_months.patient_id,
      latest_blood_pressures_per_patient_per_months.registration_facility_id,
      latest_blood_pressures_per_patient_per_months.assigned_facility_id,
      latest_blood_pressures_per_patient_per_months.patient_status,
      latest_blood_pressures_per_patient_per_months.bp_facility_id,
      latest_blood_pressures_per_patient_per_months.bp_recorded_at,
      latest_blood_pressures_per_patient_per_months.patient_recorded_at,
      latest_blood_pressures_per_patient_per_months.systolic,
      latest_blood_pressures_per_patient_per_months.diastolic,
      latest_blood_pressures_per_patient_per_months.deleted_at,
      latest_blood_pressures_per_patient_per_months.medical_history_hypertension,
      latest_blood_pressures_per_patient_per_months.month,
      latest_blood_pressures_per_patient_per_months.quarter,
      latest_blood_pressures_per_patient_per_months.year
     FROM latest_blood_pressures_per_patient_per_months
    ORDER BY latest_blood_pressures_per_patient_per_months.patient_id, latest_blood_pressures_per_patient_per_months.bp_recorded_at DESC, latest_blood_pressures_per_patient_per_months.bp_id;
  SQL
  add_index "latest_blood_pressures_per_patients", ["bp_id"], name: "index_latest_blood_pressures_per_patients", unique: true

end

Suggestion / request: use Dependabot to keep dependencies up-to-date

First of all, thanks for Simple.org!

I've got a suggestion / request: would you be up for using Dependabot to automatically create dependency update PRs for this repo? I ran it against my fork and it generated these PRs. I'll port the ffi, nokogiri and bootstrap ones across to this repo now because they're security related.

I built Dependabot, but I'm honestly only suggesting it because I hope it can save you some time. I'd love any feedback, and obviously having open source repos using Dependabot helps boost its profile, but if it's not helpful to you then it's not really worth anything.

You can install it from here or here if you decide to give it a try. It's been through GitHub's security testing (to be allowed in the GitHub Marketplace) and is used by a few thousand organisations, and the source code is here.

:octocat:

Problems with bin/setup - fails to install ruby 2.6.6 using rbenv on mac Big Sur

installing the appropriate version of ruby on Macbook Big Sur is some how challenging.
rbenv fails to install needed ruby version on Mac OSX Big Sur - 11.*
after a lot of different failed options, the below command is worked to install the needed ruby version on Mac Big Sur,

$ CFLAGS="-Wno-error=implicit-function-declaration" rbenv install 2.6.6

Full build is failing locally

Trying to run the full build fails for me locally in master. I'm running on a Mac, Postgres 10, fairly standard homebrew setup for all the dependencies.

All the failing tests appear to be fine Semaphore and other developer's machines, so I'm guessing this may be a timezone or configuration issue.

/cc @harimohanraj89

Full build log below:

�[33m[rspec-sidekiq] WARNING! Sidekiq will *NOT* process jobs in this environment. See https://github.com/philostler/rspec-sidekiq/wiki/FAQ-&-Troubleshooting�[0m
..........................................................................................................................................................................................................................................................................................................................................F......................................FFF..F.FF....FFFF.F........................................................................................................................................................................................................................F.......................................FF...F.F.F..F...FFFF.FFFF..................................................................................................................................................................Capybara starting Puma...
* Version 4.3.3 , codename: Mysterious Traveller
* Min threads: 0, max threads: 4
* Listening on tcp://127.0.0.1:52603
...............*...*......F............*************....*.F.........................*..*.F....................................................................................................****************...[DATA FIXED]user: Hyman Mohr, source: Facility 8833, destination: Facility 8832, patients: 3, BPs: 3, blood sugars: 3, appointments: 3, prescriptions: 3
.[DATA FIXED]user: Werner Bartoletti, source: Facility 8835, destination: Facility 8834, patients: 3, BPs: 3, blood sugars: 3, appointments: 3, prescriptions: 3
.[DATA FIXED]user: Mrs. Amari O'Keefe, source: Facility 8837, destination: Facility 8836, patients: 3, BPs: 3, blood sugars: 3, appointments: 3, prescriptions: 3
.[DATA FIXED]user: Amely Dibbert, source: Facility 8839, destination: Facility 8838, patients: 3, BPs: 3, blood sugars: 3, appointments: 3, prescriptions: 3
.[DATA FIXED]user: Zack Zulauf II, source: Facility 8841, destination: Facility 8840, patients: 3, BPs: 3, blood sugars: 3, appointments: 3, prescriptions: 3
..............................................................................................................................................................................................................................................................................................................................................................................................................................................F..........F................................................................................................................................................................................................................................................................................................................................FFF..F................................FFFFF..F................................................................................

Pending: (Failures listed here are expected and do not affect your suite's status)

  1) test protocol screen functionality protocol landing page JS specs are currently disabled
     # No reason given
     # ./spec/features/admin/protocols/index_spec.rb:44

  2) test protocol detail page functionality protocol show page javascript based test JS specs are currently disabled
     # No reason given
     # ./spec/features/admin/protocols/show_spec.rb:54

  3) Admins index shows all email_authentications and roles
     # Temporarily disabled with xfeature
     # ./spec/features/admins_spec.rb:13

  4) Admins editing email_authentications should allow changing facility groups
     # Temporarily skipped with xit
     # ./spec/features/admins_spec.rb:38

  5) Admins sending invitations to supervisors allows sending new invitations
     # Temporarily disabled with xfeature
     # ./spec/features/admins_spec.rb:70

  6) Admins sending invitations to supervisors sends an invite email
     # Temporarily disabled with xfeature
     # ./spec/features/admins_spec.rb:77

  7) Admins sending invitations to supervisors creates the user pending invitation
     # Temporarily disabled with xfeature
     # ./spec/features/admins_spec.rb:84

  8) Admins sending invitations to organization owners allows sending new invitations
     # Temporarily disabled with xfeature
     # ./spec/features/admins_spec.rb:112

  9) Admins sending invitations to organization owners sends an invite email
     # Temporarily disabled with xfeature
     # ./spec/features/admins_spec.rb:119

  10) Admins sending invitations to organization owners creates the user pending invitation
     # Temporarily disabled with xfeature
     # ./spec/features/admins_spec.rb:126

  11) Admins association email_authentications with their access control groups inviting supervisors associates new supervisors to facility groups
     # Temporarily disabled with xfeature
     # ./spec/features/admins_spec.rb:154

  12) Admins association email_authentications with their access control groups inviting Analyst associates new analysts to facility groups
     # Temporarily disabled with xfeature
     # ./spec/features/admins_spec.rb:174

  13) Admins association email_authentications with their access control groups inviting organization_owners associates new supervisors to facility groups
     # Temporarily disabled with xfeature
     # ./spec/features/admins_spec.rb:194

  14) Admins inviting Counsellors associates new counsellor to facility group
     # Temporarily disabled with xfeature
     # ./spec/features/admins_spec.rb:223

  15) Admins accepting invitations allows the user to set a password
     # Temporarily disabled with xfeature
     # ./spec/features/admins_spec.rb:237

  16) To test overdue appointment functionality JS specs are currently disabled
     # No reason given
     # ./spec/features/appointments/index_spec.rb:107

  17) Verify Dashboard Verify organization is displayed in dashboard
     # Temporarily skipped with xit
     # ./spec/features/organizations/index_spec.rb:20

  18) To test adherence followup patient functionality JS specs are currently disabled
     # No reason given
     # ./spec/features/patients/index_spec.rb:74

  19) ApiVersionGenerator generates the scaffold required to migrate to a new API version copy current specs for the give current version api specs
     # Temporarily skipped with xdescribe
     # ./spec/lib/generators/api_version/api_version_generator_spec.rb:31

  20) ApiVersionGenerator generates the scaffold required to migrate to a new API version copy current specs for the give current version controller specs
     # Temporarily skipped with xdescribe
     # ./spec/lib/generators/api_version/api_version_generator_spec.rb:41

  21) ApiVersionGenerator generates the scaffold required to migrate to a new API version copy current specs for the give current version payload specs 
     # Temporarily skipped with xdescribe
     # ./spec/lib/generators/api_version/api_version_generator_spec.rb:50

  22) ApiVersionGenerator generates the scaffold required to migrate to a new API version copy current specs for the give current version request specs
     # Temporarily skipped with xdescribe
     # ./spec/lib/generators/api_version/api_version_generator_spec.rb:59

  23) ApiVersionGenerator generates the scaffold required to migrate to a new API version copy current specs for the give current version shared examples for request specs
     # Temporarily skipped with xdescribe
     # ./spec/lib/generators/api_version/api_version_generator_spec.rb:68

  24) ApiVersionGenerator generates the scaffold required to migrate to a new API version copy current specs for the give current version transformer specs
     # Temporarily skipped with xdescribe
     # ./spec/lib/generators/api_version/api_version_generator_spec.rb:77

  25) ApiVersionGenerator generates the scaffold required to migrate to a new API version creates controllers for the given current version creates controller files
     # Temporarily skipped with xdescribe
     # ./spec/lib/generators/api_version/api_version_generator_spec.rb:88

  26) ApiVersionGenerator generates the scaffold required to migrate to a new API version creates controllers for the given current version creates template controllers for the given current version
     # Temporarily skipped with xdescribe
     # ./spec/lib/generators/api_version/api_version_generator_spec.rb:92

  27) ApiVersionGenerator generates the scaffold required to migrate to a new API version creates transformers for the given current version creates transformers directory for the new version
     # Temporarily skipped with xdescribe
     # ./spec/lib/generators/api_version/api_version_generator_spec.rb:110

  28) ApiVersionGenerator generates the scaffold required to migrate to a new API version creates transformers for the given current version creates template transformers for the given current version
     # Temporarily skipped with xdescribe
     # ./spec/lib/generators/api_version/api_version_generator_spec.rb:114

  29) ApiVersionGenerator generates the scaffold required to migrate to a new API version creates validators for the given current version creates validators directory for the new version
     # Temporarily skipped with xdescribe
     # ./spec/lib/generators/api_version/api_version_generator_spec.rb:132

  30) ApiVersionGenerator generates the scaffold required to migrate to a new API version creates validators for the given current version creates template validators for the given current version
     # Temporarily skipped with xdescribe
     # ./spec/lib/generators/api_version/api_version_generator_spec.rb:136

  31) ApiVersionGenerator generates the scaffold required to migrate to a new API version creates views for the given current version creates views directory for the new version
     # Temporarily skipped with xdescribe
     # ./spec/lib/generators/api_version/api_version_generator_spec.rb:154

  32) ApiVersionGenerator generates the scaffold required to migrate to a new API version creates views for the given current version creates template views for the given current version
     # Temporarily skipped with xdescribe
     # ./spec/lib/generators/api_version/api_version_generator_spec.rb:158

  33) ApiVersionGenerator generates the scaffold required to migrate to a new API version creates schema for the given current version creates schema directory for the new version
     # Temporarily skipped with xdescribe
     # ./spec/lib/generators/api_version/api_version_generator_spec.rb:169

  34) ApiVersionGenerator generates the scaffold required to migrate to a new API version creates schema for the given current version creates template schema for the given current version
     # Temporarily skipped with xdescribe
     # ./spec/lib/generators/api_version/api_version_generator_spec.rb:173

Failures:

  1) Api::V2::FacilitiesController a working V2 sync controller sending records GET sync: send data from server to device; batching Returns all the records on server over multiple small batches
     Failure/Error: expect(received_records.count).to eq model.count

       expected: 11
            got: 5

       (compared using ==)
     # ./spec/controllers/api/v2/facilities_controller_spec.rb:91:in `block (5 levels) in <top (required)>'

  2) Api::V2::PatientsController behaves like a sync controller that audits the data access creates an audit log for data synced from user creates an audit log for new data created by the user
     Failure/Error:
       expect(AuditLogger)
         .to receive(:info).with({ user: request_user.id,
                                   auditable_type: auditable_type,
                                   auditable_id: record[:id],
                                   action: 'create',
                                   time: Time.current }.to_json)

       (#<Logger:0x00007fe3cb310450 @level=0, @progname=nil, @default_formatter=#<Logger::Formatter:0x00007fe3cb310400 @datetime_format=nil>, @formatter=#<AuditLogFormatter:0x00007fe3cb3102e8>, @logdev=#<Logger::LogDevice:0x00007fe3cb3103b0 @shift_period_suffix="%Y%m%d", @shift_size=1048576, @shift_age=0, @filename="/Users/rsanheim/src/simpledotorg/simple-server/log/audit.log", @dev=#<File:/Users/rsanheim/src/simpledotorg/simple-server/log/audit.log>, @mon_owner=nil, @mon_count=0, @mon_mutex=#<Thread::Mutex:0x00007fe3cb310360>>>).info("{\"user\":\"572de78a-4d8d-4038-a847-5dbeed44114c\",\"auditable_type\":\"Patient\",\"auditable_id\":\...9b5b3fc3-f209-4217-9f4e-3f203382f574\",\"action\":\"create\",\"time\":\"2020-04-28T19:24:43.951Z\"}")
           expected: 1 time with arguments: ("{\"user\":\"572de78a-4d8d-4038-a847-5dbeed44114c\",\"auditable_type\":\"Patient\",\"auditable_id\":\...9b5b3fc3-f209-4217-9f4e-3f203382f574\",\"action\":\"create\",\"time\":\"2020-04-28T19:24:43.951Z\"}")
           received: 0 times
     Shared Example Group: "a sync controller that audits the data access" called from ./spec/controllers/api/v2/patients_controller_spec.rb:27
     # ./spec/controllers/shared_examples/shared_spec_for_sync_controller.rb:471:in `block (4 levels) in <top (required)>'

  3) Api::V2::PatientsController behaves like a sync controller that audits the data access creates an audit log for data synced from user creates an audit log for data updated by the user
     Failure/Error:
       expect(AuditLogger)
         .to receive(:info).with({ user: request_user.id,
                                   auditable_type: auditable_type,
                                   auditable_id: record[:id],
                                   action: 'update',
                                   time: Time.current }.to_json)

       (#<Logger:0x00007fe3cb310450 @level=0, @progname=nil, @default_formatter=#<Logger::Formatter:0x00007fe3cb310400 @datetime_format=nil>, @formatter=#<AuditLogFormatter:0x00007fe3cb3102e8>, @logdev=#<Logger::LogDevice:0x00007fe3cb3103b0 @shift_period_suffix="%Y%m%d", @shift_size=1048576, @shift_age=0, @filename="/Users/rsanheim/src/simpledotorg/simple-server/log/audit.log", @dev=#<File:/Users/rsanheim/src/simpledotorg/simple-server/log/audit.log>, @mon_owner=nil, @mon_count=0, @mon_mutex=#<Thread::Mutex:0x00007fe3cb310360>>>).info("{\"user\":\"8acdc8d1-2b0c-4130-813c-ac40f4265b69\",\"auditable_type\":\"Patient\",\"auditable_id\":\...d775001b-9feb-43c0-9fbe-0069f9aa33e7\",\"action\":\"update\",\"time\":\"2020-04-28T19:24:44.211Z\"}")
           expected: 1 time with arguments: ("{\"user\":\"8acdc8d1-2b0c-4130-813c-ac40f4265b69\",\"auditable_type\":\"Patient\",\"auditable_id\":\...d775001b-9feb-43c0-9fbe-0069f9aa33e7\",\"action\":\"update\",\"time\":\"2020-04-28T19:24:44.211Z\"}")
           received: 0 times
     Shared Example Group: "a sync controller that audits the data access" called from ./spec/controllers/api/v2/patients_controller_spec.rb:27
     # ./spec/controllers/shared_examples/shared_spec_for_sync_controller.rb:487:in `block (4 levels) in <top (required)>'

  4) Api::V2::PatientsController behaves like a sync controller that audits the data access creates an audit log for data synced from user creates an audit log for data touched by the user
     Failure/Error:
       expect(AuditLogger)
         .to receive(:info).with({ user: request_user.id,
                                   auditable_type: auditable_type,
                                   auditable_id: record[:id],
                                   action: 'touch',
                                   time: Time.current }.to_json)

       (#<Logger:0x00007fe3cb310450 @level=0, @progname=nil, @default_formatter=#<Logger::Formatter:0x00007fe3cb310400 @datetime_format=nil>, @formatter=#<AuditLogFormatter:0x00007fe3cb3102e8>, @logdev=#<Logger::LogDevice:0x00007fe3cb3103b0 @shift_period_suffix="%Y%m%d", @shift_size=1048576, @shift_age=0, @filename="/Users/rsanheim/src/simpledotorg/simple-server/log/audit.log", @dev=#<File:/Users/rsanheim/src/simpledotorg/simple-server/log/audit.log>, @mon_owner=nil, @mon_count=0, @mon_mutex=#<Thread::Mutex:0x00007fe3cb310360>>>).info("{\"user\":\"deaf18df-267c-4ad3-926f-1e87d98dd937\",\"auditable_type\":\"Patient\",\"auditable_id\":\..."9bf8436b-c0ec-4744-914f-dab4b1f5e026\",\"action\":\"touch\",\"time\":\"2020-04-28T19:24:44.392Z\"}")
           expected: 1 time with arguments: ("{\"user\":\"deaf18df-267c-4ad3-926f-1e87d98dd937\",\"auditable_type\":\"Patient\",\"auditable_id\":\..."9bf8436b-c0ec-4744-914f-dab4b1f5e026\",\"action\":\"touch\",\"time\":\"2020-04-28T19:24:44.392Z\"}")
           received: 0 times
     Shared Example Group: "a sync controller that audits the data access" called from ./spec/controllers/api/v2/patients_controller_spec.rb:27
     # ./spec/controllers/shared_examples/shared_spec_for_sync_controller.rb:504:in `block (4 levels) in <top (required)>'

  5) Api::V2::PatientsController POST sync: send data from device to server; behaves like a working sync controller creating records creates new records creates new records without any associations
     Failure/Error: expect(model.count).to eq 10

       expected: 10
            got: 2

       (compared using ==)
     Shared Example Group: "a working sync controller creating records" called from ./spec/controllers/api/v2/patients_controller_spec.rb:30
     # ./spec/controllers/shared_examples/shared_spec_for_sync_controller.rb:110:in `block (3 levels) in <top (required)>'

  6) Api::V2::PatientsController POST sync: send data from device to server; behaves like a working sync controller creating records creates new records returns errors for some invalid records, and accepts others
     Failure/Error: expect(response_errors.count).to eq 5

       expected: 5
            got: 8

       (compared using ==)
     Shared Example Group: "a working sync controller creating records" called from ./spec/controllers/api/v2/patients_controller_spec.rb:30
     # ./spec/controllers/shared_examples/shared_spec_for_sync_controller.rb:129:in `block (3 levels) in <top (required)>'

  7) Api::V2::PatientsController POST sync: send data from device to server; creates new patients creates new patients
     Failure/Error: expect(Patient.count).to eq 3

       expected: 3
            got: 1

       (compared using ==)
     # ./spec/controllers/api/v2/patients_controller_spec.rb:42:in `block (4 levels) in <top (required)>'

  8) Api::V2::PatientsController POST sync: send data from device to server; creates new patients associates registration user with the patients
     Failure/Error: expect(Patient.count).to eq 1

       expected: 1
            got: 0

       (compared using ==)
     # ./spec/controllers/api/v2/patients_controller_spec.rb:88:in `block (4 levels) in <top (required)>'

  9) Api::V2::PatientsController POST sync: send data from device to server; creates new patients associates registration facility with the patients
     Failure/Error: expect(Patient.count).to eq 1

       expected: 1
            got: 0

       (compared using ==)
     # ./spec/controllers/api/v2/patients_controller_spec.rb:95:in `block (4 levels) in <top (required)>'

  10) Api::V2::PatientsController POST sync: send data from device to server; updates patients with only updated patient attributes
      Failure/Error:
        expect(db_patient.attributes.with_payload_keys.with_int_timestamps
                 .except('address_id')
                 .except('registration_user_id')
                 .except('registration_facility_id')
                 .except('recorded_at')
                 .except('test_data'))
          .to eq(updated_patient.with_int_timestamps)

        expected: {"id"=>"121567b0-e035-400e-9311-8e6a4834ab3c", "full_name"=>"Marcelo Herman MD", "age"=>nil, "gender"...nsellor"=>false, "could_not_contact_reason"=>nil, "reminder_consent"=>"granted", "call_result"=>nil}
             got: {"id"=>"121567b0-e035-400e-9311-8e6a4834ab3c", "full_name"=>"Amit Kiran", "age"=>nil, "gender"=>"tran...nsellor"=>false, "could_not_contact_reason"=>nil, "reminder_consent"=>"granted", "call_result"=>nil}

        (compared using ==)

        Diff:

        @@ -6,10 +6,10 @@
         "created_at" => 1588101888,
         "date_of_birth" => Tue, 28 Apr 2020,
         "deleted_at" => nil,
        -"full_name" => "Marcelo Herman MD",
        +"full_name" => "Amit Kiran",
         "gender" => "transgender",
         "id" => "121567b0-e035-400e-9311-8e6a4834ab3c",
         "reminder_consent" => "granted",
         "status" => "active",
        -"updated_at" => 1588965888,
        +"updated_at" => 1588101888,
      # ./spec/controllers/api/v2/patients_controller_spec.rb:117:in `block (5 levels) in <top (required)>'
      # ./spec/controllers/api/v2/patients_controller_spec.rb:115:in `each'
      # ./spec/controllers/api/v2/patients_controller_spec.rb:115:in `block (4 levels) in <top (required)>'

  11) Api::V2::PatientsController POST sync: send data from device to server; updates patients with only updated address
      Failure/Error:
        expect(db_patient.address.attributes.with_payload_keys.with_int_timestamps)
          .to eq(updated_patient['address'].with_int_timestamps)

        expected: {"id"=>"9b5d4de0-bb25-4498-b82a-b241a3f62dfd", "street_address"=>"83088 Morton Ford", "village_or_col..., "created_at"=>1588101888, "updated_at"=>1588965889, "deleted_at"=>nil, "zone"=>"University Court"}
             got: {"id"=>"9b5d4de0-bb25-4498-b82a-b241a3f62dfd", "street_address"=>"838 Kenny Plaza", "village_or_colon..., "created_at"=>1588101888, "updated_at"=>1588101888, "deleted_at"=>nil, "zone"=>"University Court"}

        (compared using ==)

        Diff:
        @@ -5,8 +5,8 @@
         "id" => "9b5d4de0-bb25-4498-b82a-b241a3f62dfd",
         "pin" => "83652-5428",
         "state" => "Rhode Island",
        -"street_address" => "83088 Morton Ford",
        -"updated_at" => 1588965889,
        +"street_address" => "838 Kenny Plaza",
        +"updated_at" => 1588101888,
         "village_or_colony" => "Willow Crossing",
         "zone" => "University Court",
      # ./spec/controllers/api/v2/patients_controller_spec.rb:133:in `block (5 levels) in <top (required)>'
      # ./spec/controllers/api/v2/patients_controller_spec.rb:131:in `each'
      # ./spec/controllers/api/v2/patients_controller_spec.rb:131:in `block (4 levels) in <top (required)>'

  12) Api::V2::PatientsController POST sync: send data from device to server; updates patients with all attributes and associations updated
      Failure/Error:
        expect(db_patient.attributes.with_payload_keys.with_int_timestamps
                 .except('address_id')
                 .except('registration_user_id')
                 .except('registration_facility_id')
                 .except('recorded_at')
                 .except('test_data'))
          .to eq(updated_patient.except('address', 'phone_numbers', 'business_identifiers'))

        expected: {"id"=>"1b012478-2530-4665-ba40-ca609e372510", "full_name"=>"Trycia Hills", "age"=>nil, "gender"=>"tr...nsellor"=>false, "could_not_contact_reason"=>nil, "reminder_consent"=>"granted", "call_result"=>nil}
             got: {"id"=>"1b012478-2530-4665-ba40-ca609e372510", "full_name"=>"Anjum Bharathi", "age"=>nil, "gender"=>"...nsellor"=>false, "could_not_contact_reason"=>nil, "reminder_consent"=>"granted", "call_result"=>nil}

        (compared using ==)

        Diff:

        @@ -6,10 +6,10 @@
         "created_at" => 1588101889,
         "date_of_birth" => Tue, 28 Apr 2020,
         "deleted_at" => nil,
        -"full_name" => "Trycia Hills",
        +"full_name" => "Anjum Bharathi",
         "gender" => "transgender",
         "id" => "1b012478-2530-4665-ba40-ca609e372510",
         "reminder_consent" => "granted",
         "status" => "active",
        -"updated_at" => 1588965889,
        +"updated_at" => 1588101889,
      # ./spec/controllers/api/v2/patients_controller_spec.rb:160:in `block (5 levels) in <top (required)>'
      # ./spec/controllers/api/v2/patients_controller_spec.rb:157:in `each'
      # ./spec/controllers/api/v2/patients_controller_spec.rb:157:in `block (4 levels) in <top (required)>'

  13) Api::V3::FacilitiesController a working Current sync controller sending records GET sync: send data from server to device; batching Returns all the records on server over multiple small batches
      Failure/Error: expect(received_records.count).to eq model.count

        expected: 11
             got: 5

        (compared using ==)
      # ./spec/controllers/api/v3/facilities_controller_spec.rb:91:in `block (5 levels) in <top (required)>'

  14) Api::V3::PatientsController behaves like a sync controller that audits the data access creates an audit log for data synced from user creates an audit log for new data created by the user
      Failure/Error:
        expect(AuditLogger)
          .to receive(:info).with({ user: request_user.id,
                                    auditable_type: auditable_type,
                                    auditable_id: record[:id],
                                    action: 'create',
                                    time: Time.current }.to_json)

        (#<Logger:0x00007fe3cb310450 @level=0, @progname=nil, @default_formatter=#<Logger::Formatter:0x00007fe3cb310400 @datetime_format=nil>, @formatter=#<AuditLogFormatter:0x00007fe3cb3102e8>, @logdev=#<Logger::LogDevice:0x00007fe3cb3103b0 @shift_period_suffix="%Y%m%d", @shift_size=1048576, @shift_age=0, @filename="/Users/rsanheim/src/simpledotorg/simple-server/log/audit.log", @dev=#<File:/Users/rsanheim/src/simpledotorg/simple-server/log/audit.log>, @mon_owner=nil, @mon_count=0, @mon_mutex=#<Thread::Mutex:0x00007fe3cb310360>>>).info("{\"user\":\"bafe4fbf-cb11-4af3-88cc-a27882cb47fe\",\"auditable_type\":\"Patient\",\"auditable_id\":\...e27fb364-39d3-401c-b551-106f56922f02\",\"action\":\"create\",\"time\":\"2020-04-28T19:26:43.386Z\"}")
            expected: 1 time with arguments: ("{\"user\":\"bafe4fbf-cb11-4af3-88cc-a27882cb47fe\",\"auditable_type\":\"Patient\",\"auditable_id\":\...e27fb364-39d3-401c-b551-106f56922f02\",\"action\":\"create\",\"time\":\"2020-04-28T19:26:43.386Z\"}")
            received: 0 times
      Shared Example Group: "a sync controller that audits the data access" called from ./spec/controllers/api/v3/patients_controller_spec.rb:27
      # ./spec/controllers/shared_examples/shared_spec_for_sync_controller.rb:471:in `block (4 levels) in <top (required)>'

  15) Api::V3::PatientsController behaves like a sync controller that audits the data access creates an audit log for data synced from user creates an audit log for data updated by the user
      Failure/Error:
        expect(AuditLogger)
          .to receive(:info).with({ user: request_user.id,
                                    auditable_type: auditable_type,
                                    auditable_id: record[:id],
                                    action: 'update',
                                    time: Time.current }.to_json)

        (#<Logger:0x00007fe3cb310450 @level=0, @progname=nil, @default_formatter=#<Logger::Formatter:0x00007fe3cb310400 @datetime_format=nil>, @formatter=#<AuditLogFormatter:0x00007fe3cb3102e8>, @logdev=#<Logger::LogDevice:0x00007fe3cb3103b0 @shift_period_suffix="%Y%m%d", @shift_size=1048576, @shift_age=0, @filename="/Users/rsanheim/src/simpledotorg/simple-server/log/audit.log", @dev=#<File:/Users/rsanheim/src/simpledotorg/simple-server/log/audit.log>, @mon_owner=nil, @mon_count=0, @mon_mutex=#<Thread::Mutex:0x00007fe3cb310360>>>).info("{\"user\":\"f67ae547-8aa6-4f34-aeb4-6ac10870b55f\",\"auditable_type\":\"Patient\",\"auditable_id\":\...89fd77a7-68e5-440f-8e76-d16f91ae72d7\",\"action\":\"update\",\"time\":\"2020-04-28T19:26:43.619Z\"}")
            expected: 1 time with arguments: ("{\"user\":\"f67ae547-8aa6-4f34-aeb4-6ac10870b55f\",\"auditable_type\":\"Patient\",\"auditable_id\":\...89fd77a7-68e5-440f-8e76-d16f91ae72d7\",\"action\":\"update\",\"time\":\"2020-04-28T19:26:43.619Z\"}")
            received: 0 times
      Shared Example Group: "a sync controller that audits the data access" called from ./spec/controllers/api/v3/patients_controller_spec.rb:27
      # ./spec/controllers/shared_examples/shared_spec_for_sync_controller.rb:487:in `block (4 levels) in <top (required)>'

  16) Api::V3::PatientsController POST sync: send data from device to server; behaves like a working sync controller creating records creates new records creates new records without any associations
      Failure/Error: expect(model.count).to eq 10

        expected: 10
             got: 4

        (compared using ==)
      Shared Example Group: "a working sync controller creating records" called from ./spec/controllers/api/v3/patients_controller_spec.rb:30
      # ./spec/controllers/shared_examples/shared_spec_for_sync_controller.rb:110:in `block (3 levels) in <top (required)>'

  17) Api::V3::PatientsController POST sync: send data from device to server; behaves like a working sync controller creating records creates new records returns errors for some invalid records, and accepts others
      Failure/Error: expect(response_errors.count).to eq 5

        expected: 5
             got: 8

        (compared using ==)
      Shared Example Group: "a working sync controller creating records" called from ./spec/controllers/api/v3/patients_controller_spec.rb:30
      # ./spec/controllers/shared_examples/shared_spec_for_sync_controller.rb:129:in `block (3 levels) in <top (required)>'

  18) Api::V3::PatientsController POST sync: send data from device to server; creates new patients sets the recorded_at sent in the params
      Failure/Error: patient_in_db = Patient.find(patient.id)

      ActiveRecord::RecordNotFound:
        Couldn't find Patient with 'id'=9ac6cdd7-0168-4ed7-ba8c-e02d7dce957b [WHERE "patients"."deleted_at" IS NULL]
      # ./spec/controllers/api/v3/patients_controller_spec.rb:54:in `block (4 levels) in <top (required)>'

  19) Api::V3::PatientsController POST sync: send data from device to server; creates new patients creates new patients without business identifiers
      Failure/Error: expect(Patient.count).to eq 1

        expected: 1
             got: 0

        (compared using ==)
      # ./spec/controllers/api/v3/patients_controller_spec.rb:99:in `block (4 levels) in <top (required)>'

  20) Api::V3::PatientsController POST sync: send data from device to server; creates new patients recorded_at is not sent sets recorded_at to the earliest bp's recorded_at in case patient is synced later
      Failure/Error: patient_in_db = Patient.find(patient.id)

      ActiveRecord::RecordNotFound:
        Couldn't find Patient with 'id'=70ea4749-baca-43c0-a602-2d36754c75a7 [WHERE "patients"."deleted_at" IS NULL]
      # ./spec/controllers/api/v3/patients_controller_spec.rb:78:in `block (5 levels) in <top (required)>'

  21) Api::V3::PatientsController POST sync: send data from device to server; updates patients with only updated patient attributes
      Failure/Error:
        expect(db_patient.attributes.with_payload_keys.with_int_timestamps
                 .except('address_id')
                 .except('registration_user_id')
                 .except('registration_facility_id')
                 .except('test_data'))
          .to eq(updated_patient.with_int_timestamps)

        expected: {"id"=>"3234ded4-e66a-4f07-a9d9-b78940c67b13", "full_name"=>"Britney Mohr", "age"=>nil, "gender"=>"tr..._contact_reason"=>nil, "recorded_at"=>1588102008, "reminder_consent"=>"granted", "call_result"=>nil}
             got: {"id"=>"3234ded4-e66a-4f07-a9d9-b78940c67b13", "full_name"=>"Shreya Shreya", "age"=>nil, "gender"=>"t..._contact_reason"=>nil, "recorded_at"=>1588102008, "reminder_consent"=>"granted", "call_result"=>nil}

        (compared using ==)

        Diff:

        @@ -6,11 +6,11 @@
         "created_at" => 1588102008,
         "date_of_birth" => Tue, 28 Apr 2020,
         "deleted_at" => nil,
        -"full_name" => "Britney Mohr",
        +"full_name" => "Shreya Shreya",
         "gender" => "transgender",
         "id" => "3234ded4-e66a-4f07-a9d9-b78940c67b13",
         "recorded_at" => 1588102008,
         "reminder_consent" => "granted",
         "status" => "active",
        -"updated_at" => 1588966008,
        +"updated_at" => 1588102008,
      # ./spec/controllers/api/v3/patients_controller_spec.rb:137:in `block (5 levels) in <top (required)>'
      # ./spec/controllers/api/v3/patients_controller_spec.rb:135:in `each'
      # ./spec/controllers/api/v3/patients_controller_spec.rb:135:in `block (4 levels) in <top (required)>'

  22) Api::V3::PatientsController POST sync: send data from device to server; updates patients with only updated address
      Failure/Error:
        expect(db_patient.address.attributes.with_payload_keys.with_int_timestamps)
          .to eq(updated_patient['address'].with_int_timestamps)

        expected: {"id"=>"a542fb7d-0030-40d8-a634-9ceae3414444", "street_address"=>"8603 Padberg Grove", "village_or_co...432", "created_at"=>1588102008, "updated_at"=>1588966008, "deleted_at"=>nil, "zone"=>"Park Gardens"}
             got: {"id"=>"a542fb7d-0030-40d8-a634-9ceae3414444", "street_address"=>"58899 Anibal Via", "village_or_colo...432", "created_at"=>1588102008, "updated_at"=>1588102008, "deleted_at"=>nil, "zone"=>"Park Gardens"}

        (compared using ==)

        Diff:
        @@ -5,8 +5,8 @@
         "id" => "a542fb7d-0030-40d8-a634-9ceae3414444",
         "pin" => "53972-3432",
         "state" => "New Jersey",
        -"street_address" => "8603 Padberg Grove",
        -"updated_at" => 1588966008,
        +"street_address" => "58899 Anibal Via",
        +"updated_at" => 1588102008,
         "village_or_colony" => "Park Square",
         "zone" => "Park Gardens",
      # ./spec/controllers/api/v3/patients_controller_spec.rb:152:in `block (5 levels) in <top (required)>'
      # ./spec/controllers/api/v3/patients_controller_spec.rb:150:in `each'
      # ./spec/controllers/api/v3/patients_controller_spec.rb:150:in `block (4 levels) in <top (required)>'

  23) Api::V3::PatientsController POST sync: send data from device to server; updates patients with only updated phone numbers
      Failure/Error: expect(PatientPhoneNumber.updated_on_server_since(sync_time).count).to eq 3

        expected: 3
             got: 1

        (compared using ==)
      # ./spec/controllers/api/v3/patients_controller_spec.rb:162:in `block (4 levels) in <top (required)>'

  24) Api::V3::PatientsController POST sync: send data from device to server; updates patients with all attributes and associations updated updates non-nested fields
      Failure/Error:
        expect(db_patient.attributes.with_payload_keys.with_int_timestamps
                 .except('address_id')
                 .except('registration_user_id')
                 .except('registration_facility_id')
                 .except('test_data'))
          .to eq(updated_patient.except('address', 'phone_numbers', 'business_identifiers'))

        expected: {"id"=>"cc980402-6284-4628-a373-ab28b80b74c3", "full_name"=>"Wilbert Schoen", "age"=>nil, "gender"=>"..._contact_reason"=>nil, "recorded_at"=>1588102009, "reminder_consent"=>"granted", "call_result"=>nil}
             got: {"id"=>"cc980402-6284-4628-a373-ab28b80b74c3", "full_name"=>"Rohit Aditya", "age"=>nil, "gender"=>"ma..._contact_reason"=>nil, "recorded_at"=>1588102009, "reminder_consent"=>"granted", "call_result"=>nil}

        (compared using ==)

        Diff:

        @@ -6,11 +6,11 @@
         "created_at" => 1588102009,
         "date_of_birth" => Tue, 28 Apr 2020,
         "deleted_at" => nil,
        -"full_name" => "Wilbert Schoen",
        +"full_name" => "Rohit Aditya",
         "gender" => "male",
         "id" => "cc980402-6284-4628-a373-ab28b80b74c3",
         "recorded_at" => 1588102009,
         "reminder_consent" => "granted",
         "status" => "active",
        -"updated_at" => 1588966009,
        +"updated_at" => 1588102009,
      # ./spec/controllers/api/v3/patients_controller_spec.rb:183:in `block (6 levels) in <top (required)>'
      # ./spec/controllers/api/v3/patients_controller_spec.rb:180:in `each'
      # ./spec/controllers/api/v3/patients_controller_spec.rb:180:in `block (5 levels) in <top (required)>'

  25) Api::V3::PatientsController POST sync: send data from device to server; updates patients with all attributes and associations updated updates address
      Failure/Error:
        expect(db_patient.address.attributes.with_payload_keys.with_int_timestamps)
          .to eq(updated_patient['address'])

        expected: {"id"=>"6315a3de-d56a-4d78-a36a-3e99f814a128", "street_address"=>"7256 Lehner Shore", "village_or_col...-0375", "created_at"=>1588102010, "updated_at"=>1588966010, "deleted_at"=>nil, "zone"=>"Pine Creek"}
             got: {"id"=>"6315a3de-d56a-4d78-a36a-3e99f814a128", "street_address"=>"22462 Afton Wells", "village_or_col...-0375", "created_at"=>1588102010, "updated_at"=>1588102010, "deleted_at"=>nil, "zone"=>"Pine Creek"}

        (compared using ==)

        Diff:
        @@ -5,8 +5,8 @@
         "id" => "6315a3de-d56a-4d78-a36a-3e99f814a128",
         "pin" => "61861-0375",
         "state" => "Wisconsin",
        -"street_address" => "7256 Lehner Shore",
        -"updated_at" => 1588966010,
        +"street_address" => "22462 Afton Wells",
        +"updated_at" => 1588102010,
         "village_or_colony" => "Autumn Acres",
         "zone" => "Pine Creek",
      # ./spec/controllers/api/v3/patients_controller_spec.rb:197:in `block (6 levels) in <top (required)>'
      # ./spec/controllers/api/v3/patients_controller_spec.rb:193:in `each'
      # ./spec/controllers/api/v3/patients_controller_spec.rb:193:in `block (5 levels) in <top (required)>'

  26) Api::V3::PatientsController POST sync: send data from device to server; updates patients with all attributes and associations updated updates phone numbers
      Failure/Error: expect(PatientPhoneNumber.updated_on_server_since(sync_time).count).to eq 3

        expected: 3
             got: 2

        (compared using ==)
      # ./spec/controllers/api/v3/patients_controller_spec.rb:203:in `block (5 levels) in <top (required)>'

  27) Api::V3::PatientsController POST sync: send data from device to server; updates patients with all attributes and associations updated updates business identifiers
      Failure/Error: expect(PatientBusinessIdentifier.updated_on_server_since(sync_time).count).to eq 3

        expected: 3
             got: 1

        (compared using ==)
      # ./spec/controllers/api/v3/patients_controller_spec.rb:216:in `block (5 levels) in <top (required)>'

  28) Admin User page functionality Admin User landing page verify all district should be displayed in alphabetical order in facility dropdown
      Failure/Error: expect(actual_array).to match_array(var_list.sort)

        expected collection contained:  ["All districts", "Buchho", "Hoshiarpur", "Nilenso", "Obvious"]
        actual collection contained:    ["All districts", "Bathinda", "Buchho", "Hoshiarpur", "Mansa", "Nilenso", "Obvious"]
        the extra elements were:        ["Bathinda", "Mansa"]
      # ./spec/features/admin/users/index_spec.rb:34:in `block (3 levels) in <top (required)>'

  29) To test overdue appointment functionality Page verification landing page -Facility and page dropdown 
      Failure/Error: expect(appoint_page.get_all_facility_count).to eq(7)

        expected: 7
             got: 13

        (compared using ==)
      # ./spec/features/appointments/index_spec.rb:38:in `block (3 levels) in <top (required)>'

  30) To test adherence followup patient functionality Page verification landing page -Facility and page dropdown 
      Failure/Error: expect(adherence_page.get_all_facility_count).to eq(6)

        expected: 6
             got: 12

        (compared using ==)
      # ./spec/features/patients/index_spec.rb:32:in `block (3 levels) in <top (required)>'

  31) Api::V2::PatientPayloadValidator Validations Validates that either age or date of birth is present
      Failure/Error:
        expect(new_patient_payload('address' => nil,
                                   'phone_numbers' => nil,
                                   'age' => nil,
                                   'date_of_birth' => Date.current)).to be_valid

        expected #<Api::V3::PatientPayloadValidator:0x00007fe3abf29310 @attributes={"id"=>"cdd6f0fe-59d3-4947-8972-33171cfc4d19", "full_name"=>"Anjum Madhu", "age"=>nil, "gender"=>"transgender", "date_of_birth"=>Tue, 28 Apr 2020, "status"=>"active", "age_updated_at"=>Tue, 28 Apr 2020 19:29:46 UTC +00:00, "created_at"=>Tue, 28 Apr 2020 19:29:46 UTC +00:00, "updated_at"=>Tue, 28 Apr 2020 19:29:46 UTC +00:00, "deleted_at"=>nil, "contacted_by_counsellor"=>false, "could_not_contact_reason"=>nil, "recorded_at"=>Tue, 28 Apr 2020 19:29:46 UTC +00:00, "reminder_consent"=>"granted", "call_result"=>nil, "address"=>nil, "phone_numbers"=>nil, "business_identifiers"=>[{"id"=>"7f3a6ac4-bcd9-43f2-b31e-3df789fb786f", "identifier"=>"f84bff85-3735-448f-b5cb-9093d20f6bf9", "identifier_type"=>"simple_bp_passport", "metadata_version"=>"org.simple.bppassport.meta.v1", "metadata"=>"{\"assigning_facility_id\":\"768d6b5b-79ff-4ffa-be85-ff8ea59c6f04\",\"assigning_user_id\":\"b9e9b8e4-6a9f-4673-8f2f-b09b416a13e2\"}", "created_at"=>Tue, 28 Apr 2020 19:29:46 UTC +00:00, "updated_at"=>Tue, 28 Apr 2020 19:29:46 UTC +00:00, "deleted_at"=>nil}]}, @id="cdd6f0fe-59d3-4947-8972-33171cfc4d19", @full_name="Anjum Madhu", @age=nil, @age_updated_at=Tue, 28 Apr 2020 19:29:46 UTC +00:00, @gender="transgender", @status="active", @date_of_birth=Tue, 28 Apr 2020, @created_at=Tue, 28 Apr 2020 19:29:46 UTC +00:00, @updated_at=Tue, 28 Apr 2020 19:29:46 UTC +00:00, @recorded_at=Tue, 28 Apr 2020 19:29:46 UTC +00:00, @reminder_consent="granted", @phone_numbers=nil, @address=nil, @business_identifiers=[{"id"=>"7f3a6ac4-bcd9-43f2-b31e-3df789fb786f", "identifier"=>"f84bff85-3735-448f-b5cb-9093d20f6bf9", "identifier_type"=>"simple_bp_passport", "metadata_version"=>"org.simple.bppassport.meta.v1", "metadata"=>"{\"assigning_facility_id\":\"768d6b5b-79ff-4ffa-be85-ff8ea59c6f04\",\"assigning_user_id\":\"b9e9b8e4-6a9f-4673-8f2f-b09b416a13e2\"}", "created_at"=>Tue, 28 Apr 2020 19:29:46 UTC +00:00, "updated_at"=>Tue, 28 Apr 2020 19:29:46 UTC +00:00, "deleted_at"=>nil}], @validation_context=nil, @errors=#<ActiveModel::Errors:0x00007fe3abf19c80 @base=#<Api::V3::PatientPayloadValidator:0x00007fe3abf29310 ...>, @messages={:date_of_birth=>["can't be in the future"]}, @details={:date_of_birth=>[{:error=>"can't be in the future"}]}>, @deleted_at=nil, @contacted_by_counsellor=false, @could_not_contact_reason=nil, @call_result=nil> to be valid, but got errors: Date of birth can't be in the future
      # ./spec/payloads/api/v2/patient_payload_validator_spec.rb:12:in `block (3 levels) in <top (required)>'

  32) Api::V3::PatientPayloadValidator Validations Validates that either age or date of birth is present
      Failure/Error:
        expect(new_patient_payload('address' => nil,
                                   'phone_numbers' => nil,
                                   'age' => nil,
                                   'date_of_birth' => Date.current)).to be_valid

        expected #<Api::V3::PatientPayloadValidator:0x00007fe34f1a8738 @attributes={"id"=>"e9c212e3-6899-49fb-bbba-e8f8cef3f46c", "full_name"=>"Rahul Amit", "age"=>nil, "gender"=>"male", "date_of_birth"=>Tue, 28 Apr 2020, "status"=>"active", "age_updated_at"=>Tue, 28 Apr 2020 19:29:47 UTC +00:00, "created_at"=>Tue, 28 Apr 2020 19:29:47 UTC +00:00, "updated_at"=>Tue, 28 Apr 2020 19:29:47 UTC +00:00, "deleted_at"=>nil, "contacted_by_counsellor"=>false, "could_not_contact_reason"=>nil, "recorded_at"=>Tue, 28 Apr 2020 19:29:47 UTC +00:00, "reminder_consent"=>"granted", "call_result"=>nil, "address"=>nil, "phone_numbers"=>nil, "business_identifiers"=>[{"id"=>"b0e97c79-4c0a-44d0-a1aa-5019c3627c13", "identifier"=>"eaa1fe8f-6dc8-46f6-b244-c0f81f94c016", "identifier_type"=>"simple_bp_passport", "metadata_version"=>"org.simple.bppassport.meta.v1", "metadata"=>"{\"assigning_facility_id\":\"b8d3c0ed-f33f-49cb-9bbc-ac85b979612a\",\"assigning_user_id\":\"855e1a45-e4cf-4ede-9788-94652233e295\"}", "created_at"=>Tue, 28 Apr 2020 19:29:47 UTC +00:00, "updated_at"=>Tue, 28 Apr 2020 19:29:47 UTC +00:00, "deleted_at"=>nil}]}, @id="e9c212e3-6899-49fb-bbba-e8f8cef3f46c", @full_name="Rahul Amit", @age=nil, @age_updated_at=Tue, 28 Apr 2020 19:29:47 UTC +00:00, @gender="male", @status="active", @date_of_birth=Tue, 28 Apr 2020, @created_at=Tue, 28 Apr 2020 19:29:47 UTC +00:00, @updated_at=Tue, 28 Apr 2020 19:29:47 UTC +00:00, @recorded_at=Tue, 28 Apr 2020 19:29:47 UTC +00:00, @reminder_consent="granted", @phone_numbers=nil, @address=nil, @business_identifiers=[{"id"=>"b0e97c79-4c0a-44d0-a1aa-5019c3627c13", "identifier"=>"eaa1fe8f-6dc8-46f6-b244-c0f81f94c016", "identifier_type"=>"simple_bp_passport", "metadata_version"=>"org.simple.bppassport.meta.v1", "metadata"=>"{\"assigning_facility_id\":\"b8d3c0ed-f33f-49cb-9bbc-ac85b979612a\",\"assigning_user_id\":\"855e1a45-e4cf-4ede-9788-94652233e295\"}", "created_at"=>Tue, 28 Apr 2020 19:29:47 UTC +00:00, "updated_at"=>Tue, 28 Apr 2020 19:29:47 UTC +00:00, "deleted_at"=>nil}], @validation_context=nil, @errors=#<ActiveModel::Errors:0x00007fe34f19b1f0 @base=#<Api::V3::PatientPayloadValidator:0x00007fe34f1a8738 ...>, @messages={:date_of_birth=>["can't be in the future"]}, @details={:date_of_birth=>[{:error=>"can't be in the future"}]}>, @deleted_at=nil, @contacted_by_counsellor=false, @could_not_contact_reason=nil, @call_result=nil> to be valid, but got errors: Date of birth can't be in the future
      # ./spec/payloads/api/v3/patient_payload_validator_spec.rb:12:in `block (3 levels) in <top (required)>'

  33) Patients sync pushes 5 new blood_pressures, updates 2, and pulls only updated ones
      Failure/Error:
        model
          .find(created_records.map { |record| record['id'] })
          .take(2)
          .map(&update_payload)

      ActiveRecord::RecordNotFound:
        Couldn't find all Patients with 'id': (42b111f7-77dd-405c-8436-8fff6afa2ce1, 148325f6-52ca-4950-b00a-2096fabf56a0, 081c745a-3a1a-494e-811f-f109ac2bb119, 179390ac-ba5d-477c-be44-766e418bf28c, a3e0c0cc-0ad6-4e7c-907a-124baf597289) [WHERE "patients"."deleted_at" IS NULL] (found 1 results, but was looking for 5)
      Shared Example Group: "v2 API sync requests" called from ./spec/requests/api/v2/patient_sync_spec.rb:20
      # ./spec/requests/shared_examples/api/v2/shared_spec_for_sync_requests.rb:27:in `block (2 levels) in <top (required)>'
      # ./spec/requests/shared_examples/api/v2/shared_spec_for_sync_requests.rb:82:in `block (2 levels) in <top (required)>'

  34) Patients sync pushes 3 new patients, updates only address or phone numbers, and pulls updated ones
      Failure/Error: created_patients = Patient.find(first_patients_payload.map { |patient| patient['id'] })

      ActiveRecord::RecordNotFound:
        Couldn't find all Patients with 'id': (0db0230d-6e53-4639-bc0b-1676c0e00f58, b54437e5-449b-497d-87a6-bdd98728e1fb, 09c49f97-0141-4553-a443-91bbef68cccb) [WHERE "patients"."deleted_at" IS NULL] (found 1 results, but was looking for 3)
      # ./spec/requests/api/v2/patient_sync_spec.rb:28:in `block (2 levels) in <top (required)>'

  35) Patients sync resync_token in request headers is present syncs all records from beginning if resync_token in headers is different from the one in process_token
      Failure/Error: expect(response_body[response_key].count).to eq(5)

        expected: 5
             got: 1

        (compared using ==)
      Shared Example Group: "v2 API sync requests" called from ./spec/requests/api/v2/patient_sync_spec.rb:20
      # ./spec/requests/shared_examples/api/v2/shared_spec_for_sync_requests.rb:104:in `block (3 levels) in <top (required)>'

  36) Patients sync resync_token in request headers is not present syncs normally
      Failure/Error: expect(response_body[response_key].count).to eq(5)

        expected: 5
             got: 3

        (compared using ==)
      Shared Example Group: "v2 API sync requests" called from ./spec/requests/api/v2/patient_sync_spec.rb:20
      # ./spec/requests/shared_examples/api/v2/shared_spec_for_sync_requests.rb:142:in `block (3 levels) in <top (required)>'

  37) Patients sync pushes a new valid record and pull first time
      Failure/Error: expect(JSON(response.body)['errors']).to eq []

        expected: []
             got: [{"date_of_birth"=>["can't be in the future"], "id"=>"777e9d3e-d684-418b-acd8-3b81e5b94a43"}]

        (compared using ==)

        Diff:
        @@ -1,2 +1,3 @@
        -[]
        +[{"date_of_birth"=>["can't be in the future"],
        +  "id"=>"777e9d3e-d684-418b-acd8-3b81e5b94a43"}]
      Shared Example Group: "v3 API sync requests" called from ./spec/requests/api/v3/patient_sync_spec.rb:17
      # ./spec/requests/shared_examples/api/v3/shared_spec_for_sync_requests.rb:64:in `block (2 levels) in <top (required)>'

  38) Patients sync pushes 5 new records, updates 2, and pulls only updated ones
      Failure/Error:
        model
          .find(created_records.map { |record| record['id'] })
          .take(2)
          .map(&update_payload)

      ActiveRecord::RecordNotFound:
        Couldn't find all Patients with 'id': (9e00c701-51b3-4c99-8028-beeb69f894a3, 9f0bc52a-65f8-4e1c-8b44-cb0a53fa1d17, a8e040f7-aeec-4064-b20d-1a58f31d20fd, f98449da-1786-46d2-8342-4b6fc8ee648c, fd0f8e9f-e4f1-4350-988e-01ea7c716fb0) [WHERE "patients"."deleted_at" IS NULL] (found 3 results, but was looking for 5)
      Shared Example Group: "v3 API sync requests" called from ./spec/requests/api/v3/patient_sync_spec.rb:17
      # ./spec/requests/shared_examples/api/v3/shared_spec_for_sync_requests.rb:27:in `block (2 levels) in <top (required)>'
      # ./spec/requests/shared_examples/api/v3/shared_spec_for_sync_requests.rb:82:in `block (2 levels) in <top (required)>'

  39) Patients sync pushes 3 new patients, updates only address or phone numbers, and pulls updated ones
      Failure/Error: created_patients         = Patient.find(first_patients_payload.map { |patient| patient['id'] })

      ActiveRecord::RecordNotFound:
        Couldn't find all Patients with 'id': (3119531f-9b0b-43e1-94e9-511782bdc59c, 42eec73f-8932-4e79-919a-403adfe6364a, 080d656d-bed1-4729-98f9-57ae793a2419) [WHERE "patients"."deleted_at" IS NULL] (found 2 results, but was looking for 3)
      # ./spec/requests/api/v3/patient_sync_spec.rb:25:in `block (2 levels) in <top (required)>'

  40) Patients sync resync_token in request headers is present syncs all records from beginning if resync_token in process_token is nil
      Failure/Error: expect(response_body[response_key].count).to eq(5)

        expected: 5
             got: 1

        (compared using ==)
      Shared Example Group: "v3 API sync requests" called from ./spec/requests/api/v3/patient_sync_spec.rb:17
      # ./spec/requests/shared_examples/api/v3/shared_spec_for_sync_requests.rb:104:in `block (3 levels) in <top (required)>'

  41) Patients sync resync_token in request headers is present syncs all records from beginning if resync_token in headers is different from the one in process_token
      Failure/Error: expect(response_body[response_key].count).to eq(5)

        expected: 5
             got: 2

        (compared using ==)
      Shared Example Group: "v3 API sync requests" called from ./spec/requests/api/v3/patient_sync_spec.rb:17
      # ./spec/requests/shared_examples/api/v3/shared_spec_for_sync_requests.rb:116:in `block (3 levels) in <top (required)>'

  42) Patients sync resync_token in request headers is not present syncs normally
      Failure/Error: expect(response_body[response_key].count).to eq(5)

        expected: 5
             got: 3

        (compared using ==)
      Shared Example Group: "v3 API sync requests" called from ./spec/requests/api/v3/patient_sync_spec.rb:17
      # ./spec/requests/shared_examples/api/v3/shared_spec_for_sync_requests.rb:154:in `block (3 levels) in <top (required)>'

Finished in 7 minutes 59 seconds (files took 13.7 seconds to load)
1932 examples, 42 failures, 34 pending

Failed examples:

rspec ./spec/controllers/api/v2/facilities_controller_spec.rb:76 # Api::V2::FacilitiesController a working V2 sync controller sending records GET sync: send data from server to device; batching Returns all the records on server over multiple small batches
rspec ./spec/controllers/api/v2/patients_controller_spec.rb[1:2:1:1] # Api::V2::PatientsController behaves like a sync controller that audits the data access creates an audit log for data synced from user creates an audit log for new data created by the user
rspec ./spec/controllers/api/v2/patients_controller_spec.rb[1:2:1:2] # Api::V2::PatientsController behaves like a sync controller that audits the data access creates an audit log for data synced from user creates an audit log for data updated by the user
rspec ./spec/controllers/api/v2/patients_controller_spec.rb[1:2:1:3] # Api::V2::PatientsController behaves like a sync controller that audits the data access creates an audit log for data synced from user creates an audit log for data touched by the user
rspec ./spec/controllers/api/v2/patients_controller_spec.rb[1:3:1:1:2] # Api::V2::PatientsController POST sync: send data from device to server; behaves like a working sync controller creating records creates new records creates new records without any associations
rspec ./spec/controllers/api/v2/patients_controller_spec.rb[1:3:1:1:4] # Api::V2::PatientsController POST sync: send data from device to server; behaves like a working sync controller creating records creates new records returns errors for some invalid records, and accepts others
rspec ./spec/controllers/api/v2/patients_controller_spec.rb:39 # Api::V2::PatientsController POST sync: send data from device to server; creates new patients creates new patients
rspec ./spec/controllers/api/v2/patients_controller_spec.rb:85 # Api::V2::PatientsController POST sync: send data from device to server; creates new patients associates registration user with the patients
rspec ./spec/controllers/api/v2/patients_controller_spec.rb:92 # Api::V2::PatientsController POST sync: send data from device to server; creates new patients associates registration facility with the patients
rspec ./spec/controllers/api/v2/patients_controller_spec.rb:110 # Api::V2::PatientsController POST sync: send data from device to server; updates patients with only updated patient attributes
rspec ./spec/controllers/api/v2/patients_controller_spec.rb:127 # Api::V2::PatientsController POST sync: send data from device to server; updates patients with only updated address
rspec ./spec/controllers/api/v2/patients_controller_spec.rb:152 # Api::V2::PatientsController POST sync: send data from device to server; updates patients with all attributes and associations updated
rspec ./spec/controllers/api/v3/facilities_controller_spec.rb:76 # Api::V3::FacilitiesController a working Current sync controller sending records GET sync: send data from server to device; batching Returns all the records on server over multiple small batches
rspec ./spec/controllers/api/v3/patients_controller_spec.rb[1:2:1:1] # Api::V3::PatientsController behaves like a sync controller that audits the data access creates an audit log for data synced from user creates an audit log for new data created by the user
rspec ./spec/controllers/api/v3/patients_controller_spec.rb[1:2:1:2] # Api::V3::PatientsController behaves like a sync controller that audits the data access creates an audit log for data synced from user creates an audit log for data updated by the user
rspec ./spec/controllers/api/v3/patients_controller_spec.rb[1:3:1:1:2] # Api::V3::PatientsController POST sync: send data from device to server; behaves like a working sync controller creating records creates new records creates new records without any associations
rspec ./spec/controllers/api/v3/patients_controller_spec.rb[1:3:1:1:4] # Api::V3::PatientsController POST sync: send data from device to server; behaves like a working sync controller creating records creates new records returns errors for some invalid records, and accepts others
rspec ./spec/controllers/api/v3/patients_controller_spec.rb:48 # Api::V3::PatientsController POST sync: send data from device to server; creates new patients sets the recorded_at sent in the params
rspec ./spec/controllers/api/v3/patients_controller_spec.rb:97 # Api::V3::PatientsController POST sync: send data from device to server; creates new patients creates new patients without business identifiers
rspec ./spec/controllers/api/v3/patients_controller_spec.rb:68 # Api::V3::PatientsController POST sync: send data from device to server; creates new patients recorded_at is not sent sets recorded_at to the earliest bp's recorded_at in case patient is synced later
rspec ./spec/controllers/api/v3/patients_controller_spec.rb:129 # Api::V3::PatientsController POST sync: send data from device to server; updates patients with only updated patient attributes
rspec ./spec/controllers/api/v3/patients_controller_spec.rb:146 # Api::V3::PatientsController POST sync: send data from device to server; updates patients with only updated address
rspec ./spec/controllers/api/v3/patients_controller_spec.rb:157 # Api::V3::PatientsController POST sync: send data from device to server; updates patients with only updated phone numbers
rspec ./spec/controllers/api/v3/patients_controller_spec.rb:179 # Api::V3::PatientsController POST sync: send data from device to server; updates patients with all attributes and associations updated updates non-nested fields
rspec ./spec/controllers/api/v3/patients_controller_spec.rb:192 # Api::V3::PatientsController POST sync: send data from device to server; updates patients with all attributes and associations updated updates address
rspec ./spec/controllers/api/v3/patients_controller_spec.rb:202 # Api::V3::PatientsController POST sync: send data from device to server; updates patients with all attributes and associations updated updates phone numbers
rspec ./spec/controllers/api/v3/patients_controller_spec.rb:215 # Api::V3::PatientsController POST sync: send data from device to server; updates patients with all attributes and associations updated updates business identifiers
rspec ./spec/features/admin/users/index_spec.rb:30 # Admin User page functionality Admin User landing page verify all district should be displayed in alphabetical order in facility dropdown
rspec ./spec/features/appointments/index_spec.rb:34 # To test overdue appointment functionality Page verification landing page -Facility and page dropdown 
rspec ./spec/features/patients/index_spec.rb:28 # To test adherence followup patient functionality Page verification landing page -Facility and page dropdown 
rspec ./spec/payloads/api/v2/patient_payload_validator_spec.rb:11 # Api::V2::PatientPayloadValidator Validations Validates that either age or date of birth is present
rspec ./spec/payloads/api/v3/patient_payload_validator_spec.rb:11 # Api::V3::PatientPayloadValidator Validations Validates that either age or date of birth is present
rspec ./spec/requests/api/v2/patient_sync_spec.rb[1:3] # Patients sync pushes 5 new blood_pressures, updates 2, and pulls only updated ones
rspec ./spec/requests/api/v2/patient_sync_spec.rb:22 # Patients sync pushes 3 new patients, updates only address or phone numbers, and pulls updated ones
rspec ./spec/requests/api/v2/patient_sync_spec.rb[1:4:1] # Patients sync resync_token in request headers is present syncs all records from beginning if resync_token in headers is different from the one in process_token
rspec ./spec/requests/api/v2/patient_sync_spec.rb[1:5:1] # Patients sync resync_token in request headers is not present syncs normally
rspec ./spec/requests/api/v3/patient_sync_spec.rb[1:2] # Patients sync pushes a new valid record and pull first time
rspec ./spec/requests/api/v3/patient_sync_spec.rb[1:3] # Patients sync pushes 5 new records, updates 2, and pulls only updated ones
rspec ./spec/requests/api/v3/patient_sync_spec.rb:19 # Patients sync pushes 3 new patients, updates only address or phone numbers, and pulls updated ones
rspec ./spec/requests/api/v3/patient_sync_spec.rb[1:4:1] # Patients sync resync_token in request headers is present syncs all records from beginning if resync_token in process_token is nil
rspec ./spec/requests/api/v3/patient_sync_spec.rb[1:4:2] # Patients sync resync_token in request headers is present syncs all records from beginning if resync_token in headers is different from the one in process_token
rspec ./spec/requests/api/v3/patient_sync_spec.rb[1:5:1] # Patients sync resync_token in request headers is not present syncs normally

Coverage report generated for RSpec to /Users/rsanheim/src/simpledotorg/simple-server/coverage. 15655 / 16263 LOC (96.26%) covered.

docker-compose configuration out of date

Unable to run simple-server with current docker-compose config, which still based on ruby:2.5.1

$ docker-compose up
Your Ruby version is 2.5.1, but your Gemfile specified 2.6.6
ERROR: Service 'server' failed to build: The command '/bin/sh -c bundle _1.17.3_ install' returned a non-zero code: 18

After setting specified ruby 2.6.6 version, docker-compose setup almost finished except the following error:

server_1    | Seeding Facilities: |==========================================================|
server_1    | ⭐️  Seed complete! Elasped time less than a minute ⭐️
server_1    | rails aborted!
server_1    | ArgumentError: invalid uri scheme ''
server_1    | /usr/local/bundle/gems/redis-4.1.4/lib/redis/client.rb:439:in `_parse_options'
server_1    | /usr/local/bundle/gems/redis-4.1.4/lib/redis/client.rb:83:in `initialize'
server_1    | /usr/local/bundle/gems/redis-4.1.4/lib/redis.rb:44:in `new'
server_1    | /usr/local/bundle/gems/redis-4.1.4/lib/redis.rb:44:in `initialize'

server_1    | Tasks: TOP => db:refresh_materialized_views
server_1    | (See full trace by running task with --trace)

Seems like REDIS_URL isn't set properly or missing.

Sync patient data from server to user

  • We need to think this through
  • We need to batch records that need to be sent from server to user. Do we use pagination mechanisms? How do we define last_sync_at? Should we record server_updated_at in addition to device_updated_at?

Sync To User perf improvements

This perf improvement exercise primarily began with a buggy release that made users without block-sync enabled to start resyncing (this issue was patched later). The impact it had on the DB and app servers was completely unsustainable and made us realize that the same is going to happen to users when we start releasing block-sync for real.

We wrote a quick script that simulates what would happen if all users in India started block-level syncing on the perf envs which have real production data.

The following is the series of improvements made by looking at traces and finding bottlenecks.

Query optimizations

We ran the script for 100 users first (100 coroutines across 6-8 threads) and the bottlenecks were complete DB-bound (datadog ref):
Screenshot 2020-12-18 at 3 24 17 PM

There were mainly two slow queries, which are very similar:

# BPs belonging to the current facility's registered patients
SELECT ? FROM blood_pressures 
WHERE blood_pressures . patient_id IN( <patients subquery> ) 
AND blood_pressures . patient_id IN (<current facility patients>)
AND ( blood_pressures.updated_at >= ? )
ORDER BY blood_pressures . updated_at ASC LIMIT ? 

# BPs belonging to patients other than the current facility
SELECT ? FROM blood_pressures 
WHERE blood_pressures . patient_id IN (<patients subquery>) 
AND blood_pressures . patient_id NOT IN (<current facility patients>)
AND ( blood_pressures.updated_at >= ? )
ORDER BY blood_pressures . updated_at ASC LIMIT ? 

# The patients subquery is syncable_patients for a region
SELECT patients . id FROM (
  (SELECT patients . * FROM patients WHERE patients . registration_facility_id IN ( ? )) 
   UNION (SELECT patients . * FROM patients WHERE patients . assigned_facility_id IN ( ? ))) patients)
   UNION (SELECT patients . * FROM patients INNER JOIN appointments ON appointments . deleted_at IS ? 
                  AND appointments . patient_id = ? . id WHERE appointments . facility_id IN ( ? ))

Index on (patient_id, updated_at) instead of just updated_at

On further investigation, we found the syncable_patients subquery on its own was decent, whereas simple BP queries were far too slow.

SELECT  "blood_pressures".* FROM "blood_pressures" 
WHERE "blood_pressures"."patient_id" IN (<1k ids>)
AND (blood_pressures.updated_at >= 2018-12-31 18:30:00 BC') 
ORDER BY "blood_pressures"."updated_at" ASC LIMIT 1000

This took ~2-3s in a regular FG. We probed at some initial suspects:

  • too many Patient IDs in the IN clause - the query was slow for a handful of patients as well;
  • if indexes on updated_at and patient_id were missing - they were present;

The query explain suggested that Postgres was not using the patient_id index at all and relied solely on the updated_at index. When we tried the query without the ORDER BY updated_at clause it was drastically faster (down to ~100ms). This time it used the patient_id index which was surprising, since we already had this in the first place.

Separately, removing the updated_at index (and keeping the patient_id index) also sped up the query. The query planner now used the patient_id index (see query explain). We don't have a definite answer to why the query planner chooses one index over the other but we have a guess for why the updated_at index was slower:

  • When we do a re-sync, we search through data from the beginning of time, which means filtering by updated_at doesn't filter down any records. Narrowing records by patient_id first is more efficient.
  • When we do a regular sync, we search through the latest records (BPs added since the last sync, for example). An updated_at index does help in this case over having just a patient_id index.

To cover both the scenarios, we've added a compound index on patient_id and updated_at (#1900). The updated_at index is removed in #1901.

This change had the most impact on sync health overall. P99 latencies in the stress test went from consistently 6s+ to ~3s. This also had marked (re: insane) improvements to the DB CPU.

Splitting current_facility_records and other_facility_records

After fixing the index we found that the other_facility_records query was still slow (datadog ref).

image

This was easy to pinpoint to the NOT query.

def other_facility_records
  other_facilities_limit = limit - current_facility_records.count
  region_records
    .where.not(patient: current_facility_patients)
    .updated_on_server_since(other_facilities_processed_since, other_facilities_limit)
end

# region_records
def region_records
  model.syncable_to_region(current_sync_region)
end

One would assume this query to:

  • Fetch region_records
  • Filter them by the not condition
  • Filter them by updated_at and limit

However Postgres has no way of figuring out that the not query is a subset of region_records. The not is actually applied on the whole table and Postgres combines these at the end (EXPLAIN). This can be fixed with some query refactoring. We can tweak which patients to fetch the records for in ruby, like so:

model_sync_scope
  .where(id: current_sync_region.syncable_patients - current_facility.syncable_patients)
  .updated_on_server_since(other_facilities_processed_since, other_facilities_limit)

# model_sync_scope
def model_sync_scope
  model.with_discarded
end

Similarly since current_facility_records also filters by registered_patients, it can be made more performant:

# Before
def current_facility_records
  region_records
    .where(patient: current_facility.registered_patients.with_discarded)
    .updated_on_server_since(current_facility_processed_since, limit)
end

# After
def current_facility_records
  model_sync_scope
    .where(id: current_facility.syncable_patients)
    .updated_on_server_since(current_facility_processed_since, limit)
end

Fixed in #1897

OR instead of UNION

Doing .or(assigned_patients) instead of .union(assigned_patients) in syncable_patients makes the query slightly faster as expected.

Controller Optimizations

Activerecord instantiation

image

We noticed a lot of time being taken by Patient activerecord.instantiation. This was happening in the freshly added where clauses: An easy fix and it shaved off a handsome 15% in response time:

# Before
model_sync_scope
  .where(id: current_sync_region.syncable_patients - current_facility.syncable_patients)

# After
model_sync_scope
  .where(id: current_sync_region.syncable_patients.pluck(:id) - current_facility.syncable_patients.pluck(:id))

OJ for JSON dumping

  • Since our JSON payloads are quite large (~100kB) we expected adding a faster JSON dumping replacement to save us some time. We found OJ to be a good fit. This made JSON dumping 2.5x faster and cut roughly 10% in CPU load during stress tests.

Facilities controller

The facilities controller was firing 1+N queries to get each facility's block region id (8000 queries in total 😬 ). This warranted adding a with_block_region_id scope to fetch the block id in an includes. #1896

Note on datadog

Datadog.tracer.trace is your friend. This helped a ton in breaking apart the controller. Example usage

image

Problems with bin/setup - ruby 2.6.6 installation failed

installing the appropriate version of ruby on Macbook Big Sur is some how challenging.
rbenv fails to install needed ruby version on Mac OSX Big Sur - 11.*
after a lot of different failed options, the below command is worked to install the needed ruby version on Macbook Big Sur,

$ CFLAGS="-Wno-error=implicit-function-declaration" rbenv install 2.6.6

DB perf tracking issue

We had a DB related outage last night on August 27th. This is a tracking issue to discuss remediation steps, fixes, and to track progress.

References

Checklist

  • back off on materialized view refresh
  • measure time to refresh caches on sandbox before any changes
  • look into what it takes to upgrade Postgres RDS instance size
  • upgrade to a db.r5 r4.xlarge type database on sandbox -- these are the "latest generation memory optimized" instance types, and are supported for PostgreSQL.
    Note that we couldn't use the r5 or higher because our Postgres version isn't recent enough
  • verify
  • repeat for prod but use the r4.x2large instance
  • turn back on region cache warming (with flipper toggle) #1257
    NOTE: I got blocked here because I needed to deploy a new ENV var to production, and that requires SSH access to ship deploymentPRs
  • Merge and deploy latest deployment the new slack webhook ENV var to production simpledotorg/deployment#246 (this is required for the next step!)
  • Deploy latest production simple-server so that we get #1258 deployed - kit/hari
  • Run the cache warmer manually on production to verify the DB is fine and that we get notifications in Slack #alerts - kit/hari:
RAILS_ENV=production bundle exec rails runner "Reports::RegionCacheWarmer.call"
  • Verify production report page load times -- it should generally be 500 ms or lower for all the pages if they are all in the cache. You can find them starting at /reports/regions - kit/hari - left some notes here.
  • Update Terraform configuration to be in sync with our new database instance types simpledotorg/deployment@ef29a22 - prabhanshu
  • Stick a last refreshed at timestamp on the My Facilities pages and inform the CVHOs. #1259 - kit

Rollback plan

Dev setup on apple silicon

Going to document some things here to get a working dev setup on m1 Macs with as much native ARM as possible:

Note: Everything will just work with Rosetta, which is the current recommended way of doing things until blocker(s) below are fixed.

Working

All the following work fine with a native ARM homebrew install:

  • redis
  • postgres@10
  • yarn / node
  • rbenv
  • ruby (though see blockers below)

blockers

  • The current problem is that we use the http gem relies on a parser that does not work on m1 Macs and sounds like it may be a heavy lift to replace -- see httprb/http#630 and httprb/http#639 for context.

References

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.