simpledotorg / simple-server Goto Github PK
View Code? Open in Web Editor NEWThe web app behind Simple.org
License: MIT License
The web app behind Simple.org
License: MIT License
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.
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),
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.
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:
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.
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.
With the final bunch of queries, we tend to use only:
updated_at
index on all the models except Patientid
and updated_at
on PatientMy recommendation would be to continue to keep the existing index infrastructure and monitoring the index usage periodically and remove the non-essentials.
Before and after graphs on perf for both Block and FG,
master
code with 5% of users resyncing FacilityGroup
sync-perf-fixes
code with 5% of users resyncing Block
master
code with DB CPU utilization on 5% of users resyncing FacilityGroup
sync-perf-fixes
code with DB CPU utilization on 5% of users resyncing Block
master
code with 1 instance running (8 vCPUs)
sync-perf-fixes
code with 1 instance running (8 vCPUs)
I'm confident that these changes should work for a 5% rollout for block-sync and existing regular FG syncs as they happen.
[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.
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
We perform User login using the following code:
# app/controllers/api/v1/logins_controller.rb
user = User.find_by(phone_number: login_params[:phone_number])
This fails when multiple users have the same phone number.
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.
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
id – uuid
street address - string
colony - string
village - string
district - string
state - string
country - string
pin - string
number - string
type -string
active - boolean
Patient belongs to an address
Patient has and belongs to many phone numbers
Reports within Bathinda and Mansa facility group should be grouped by district -- currently they're not.
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
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.
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
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.
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.
I believe the build URL here is pointing to the wrong project. However, this is working. Probably why the image isn't loading up either.
last_sync_at
? Should we record server_updated_at
in addition to device_updated_at
?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.
We ran the script for 100 users first (100 coroutines across 6-8 threads) and the bottlenecks were complete DB-bound (datadog ref):
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 ( ? ))
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:
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:
updated_at
doesn't filter down any records. Narrowing records by patient_id
first is more efficient.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.
After fixing the index we found that the other_facility_records
query was still slow (datadog ref).
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:
region_records
not
conditionupdated_at
and limitHowever 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.
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))
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
Datadog.tracer.trace
is your friend. This helped a ton in breaking apart the controller. Example usage
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
How to add admin user? or
What is the default admin user and password?
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.
db.r5
r4.xlarge
type database on sandbox -- these are the "latest generation memory optimized" instance types, and are supported for PostgreSQL.r4.x2large
instancedeployment
PRsdeployment
the new slack webhook ENV var to production simpledotorg/deployment#246 (this is required for the next step!)simple-server
so that we get #1258 deployed - kit/hariRAILS_ENV=production bundle exec rails runner "Reports::RegionCacheWarmer.call"
/reports/regions
- kit/hari - left some notes here.last refreshed at
timestamp on the My Facilities pages and inform the CVHOs. #1259 - kitdisable_region_cache_warmer
feature in Flipper in production, which should bypass the cache warming - see https://github.com/simpledotorg/simple-server/pull/1257/files#diff-e5a1a9711a94012bc35fc3a30e3c2d86R28Going 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.
All the following work fine with a native ARM homebrew install:
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.