Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions app/controllers/api/v4/patient_scores_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class Api::V4::PatientScoresController < Api::V4::SyncController
def sync_to_user
__sync_to_user__("patient_scores")
end

def current_facility_records
@current_facility_records ||=
PatientScore
.for_sync
.where(patient: current_facility.prioritized_patients.select(:id))
.updated_on_server_since(current_facility_processed_since, limit)
end

def other_facility_records
other_facilities_limit = limit - current_facility_records.size
@other_facility_records ||=
PatientScore
.for_sync
.where(patient_id: current_sync_region
.syncable_patients
.where.not(registration_facility: current_facility)
.select(:id))
.updated_on_server_since(other_facilities_processed_since, other_facilities_limit)
end

private

def transform_to_response(patient_score)
Api::V4::PatientScoreTransformer.to_response(patient_score)
end
end
13 changes: 13 additions & 0 deletions app/models/patient_score.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
class PatientScore < ApplicationRecord
include Mergeable
include Discard::Model

belongs_to :patient, optional: true

validates :device_created_at, presence: true
validates :device_updated_at, presence: true
validates :score_type, presence: true
validates :score_value, presence: true, numericality: true

scope :for_sync, -> { with_discarded }
end
16 changes: 16 additions & 0 deletions app/schema/api/v4/models.rb
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,20 @@ def patient_attribute
required: %w[id patient_id height weight created_at updated_at]}
end

def patient_score
{type: :object,
properties: {
id: {"$ref" => "#/definitions/uuid"},
patient_id: {"$ref" => "#/definitions/uuid"},
score_type: {"$ref" => "#/definitions/non_empty_string"},
score_value: {type: :number},
deleted_at: {"$ref" => "#/definitions/nullable_timestamp"},
created_at: {"$ref" => "#/definitions/timestamp"},
updated_at: {"$ref" => "#/definitions/timestamp"}
},
required: %w[id patient_id score_type score_value created_at updated_at]}
end

def patient_phone_number
{
type: :object,
Expand Down Expand Up @@ -458,6 +472,8 @@ def definitions
patient: patient,
patient_attribute: patient_attribute,
patient_attributes: Api::CommonDefinitions.array_of("patient_attribute"),
patient_score: patient_score,
patient_scores: Api::CommonDefinitions.array_of("patient_score"),
patient_business_identifier: Api::V3::Models.patient_business_identifier,
patient_business_identifiers: Api::CommonDefinitions.array_of("patient_business_identifier"),
phone_number: Api::V3::Models.phone_number,
Expand Down
19 changes: 19 additions & 0 deletions app/transformers/api/v4/patient_score_transformer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class Api::V4::PatientScoreTransformer < Api::V4::Transformer
class << self
def to_response(payload)
super(payload)
.merge({
"score_type" => payload["score_type"],
"score_value" => payload["score_value"].to_f
})
end

def from_request(payload)
super(payload)
.merge({
"score_type" => payload["score_type"],
"score_value" => payload["score_value"].to_f
})
end
end
end
4 changes: 4 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@
get "sync", to: "cvd_risks#sync_to_user"
post "sync", to: "cvd_risks#sync_from_user"
end

scope :patient_scores do
get "sync", to: "patient_scores#sync_to_user"
end
end

namespace :webview do
Expand Down
24 changes: 24 additions & 0 deletions db/migrate/20260209112204_create_patient_scores.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class CreatePatientScores < ActiveRecord::Migration[6.1]
def change
unless table_exists?(:patient_scores)
create_table :patient_scores, id: :uuid do |t|
t.references :patient, null: false, foreign_key: true, type: :uuid
t.string :score_type, null: false, limit: 100
t.decimal :score_value, precision: 5, scale: 2, null: false
t.datetime :device_created_at, null: false
t.datetime :device_updated_at, null: false
t.datetime :deleted_at

t.timestamps
end
end

unless index_exists?(:patient_scores, [:patient_id, :score_type])
add_index :patient_scores, [:patient_id, :score_type]
end

unless index_exists?(:patient_scores, :updated_at)
add_index :patient_scores, :updated_at
end
end
end
104 changes: 58 additions & 46 deletions db/structure.sql
Original file line number Diff line number Diff line change
Expand Up @@ -748,9 +748,7 @@ CREATE TABLE IF NOT EXISTS simple_reporting.reporting_patient_prescriptions (
hypertension_drug_changed boolean,
diabetes_drug_changed boolean,
other_drug_changed boolean,
prescribed_statins boolean,
latest_cvd_risk_score_lower_range integer,
latest_cvd_risk_score_upper_range integer
prescribed_statins boolean
)
PARTITION BY LIST (month_date);

Expand Down Expand Up @@ -865,10 +863,7 @@ CREATE OR REPLACE FUNCTION simple_reporting.reporting_patient_prescriptions_tabl
) elem
WHERE elem->>'drug_name' ILIKE '%statin%'
)
) AS prescribed_statins,

cvd.latest_cvd_risk_score_lower_range,
cvd.latest_cvd_risk_score_upper_range
) AS prescribed_statins

FROM simple_reporting.reporting_patient_states rps
LEFT JOIN reporting_facilities assigned_facility ON rps.assigned_facility_id = assigned_facility.facility_id
Expand Down Expand Up @@ -945,27 +940,6 @@ CREATE OR REPLACE FUNCTION simple_reporting.reporting_patient_prescriptions_tabl
)
)
) prev ON TRUE

LEFT JOIN LATERAL (
SELECT
split_part(cr.risk_score,'-',1)::int AS latest_cvd_risk_score_lower_range,

COALESCE(
NULLIF(split_part(cr.risk_score,'-',2),''),
split_part(cr.risk_score,'-',1)
)::int AS latest_cvd_risk_score_upper_range

FROM cvd_risks cr
WHERE cr.patient_id = rps.patient_id
AND cr.deleted_at IS NULL
AND date_trunc('month',
timezone(current_setting('TIMEZONE'),
timezone('UTC', cr.device_updated_at))
) < (rps.month_date + interval '1 month')

ORDER BY cr.device_updated_at DESC
LIMIT 1
) cvd ON TRUE
WHERE rps.month_date = $1
AND rps.htn_care_state <> 'dead';
END;
Expand Down Expand Up @@ -2709,6 +2683,23 @@ CREATE TABLE public.patient_phone_numbers (
);


--
-- Name: patient_scores; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE public.patient_scores (
id uuid NOT NULL,
patient_id uuid NOT NULL,
score_type character varying(100) NOT NULL,
score_value numeric(5,2) NOT NULL,
device_created_at timestamp without time zone NOT NULL,
device_updated_at timestamp without time zone NOT NULL,
deleted_at timestamp without time zone,
created_at timestamp(6) without time zone NOT NULL,
updated_at timestamp(6) without time zone NOT NULL
);


--
-- Name: prescription_drugs; Type: TABLE; Schema: public; Owner: -
--
Expand Down Expand Up @@ -7424,6 +7415,14 @@ ALTER TABLE ONLY public.patient_phone_numbers
ADD CONSTRAINT patient_phone_numbers_pkey PRIMARY KEY (id);


--
-- Name: patient_scores patient_scores_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_scores
ADD CONSTRAINT patient_scores_pkey PRIMARY KEY (id);


--
-- Name: patients patients_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -8527,6 +8526,20 @@ CREATE INDEX index_patient_phone_numbers_on_dnd_status ON public.patient_phone_n
CREATE INDEX index_patient_phone_numbers_on_patient_id ON public.patient_phone_numbers USING btree (patient_id);


--
-- Name: index_patient_scores_on_patient_id_and_score_type; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX index_patient_scores_on_patient_id_and_score_type ON public.patient_scores USING btree (patient_id, score_type);


--
-- Name: index_patient_scores_on_updated_at; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX index_patient_scores_on_updated_at ON public.patient_scores USING btree (updated_at);


--
-- Name: index_patient_registrations_per_day_per_facilities; Type: INDEX; Schema: public; Owner: -
--
Expand Down Expand Up @@ -9109,24 +9122,6 @@ CREATE INDEX patient_prescriptions_assigned_organization_region_id ON ONLY simpl

CREATE INDEX index_patient_prescriptions_patient_id ON ONLY simple_reporting.reporting_patient_prescriptions USING btree (patient_id);

--
-- Name: idx_rpp_month_patient; Type: INDEX; Schema: simple_reporting; Owner: -
--

CREATE INDEX idx_rpp_month_patient ON simple_reporting.reporting_patient_prescriptions (month_date, patient_id);

--
-- Name: idx_rpp_latest_cvd_score_lower_range; Type: INDEX; Schema: simple_reporting; Owner: -
--

CREATE INDEX idx_rpp_latest_cvd_score_lower_range ON simple_reporting.reporting_patient_prescriptions (latest_cvd_risk_score_lower_range);

--
-- Name: idx_rpp_latest_cvd_score_upper_range; Type: INDEX; Schema: simple_reporting; Owner: -
--

CREATE INDEX idx_rpp_latest_cvd_score_upper_range ON simple_reporting.reporting_patient_prescriptions (latest_cvd_risk_score_upper_range);

--
-- Name: index_fs_block; Type: INDEX; Schema: simple_reporting; Owner: -
--
Expand Down Expand Up @@ -9165,6 +9160,14 @@ ALTER TABLE ONLY public.patient_phone_numbers
ADD CONSTRAINT fk_rails_0145dd0b05 FOREIGN KEY (patient_id) REFERENCES public.patients(id);


--
-- Name: patient_scores fk_rails_0209112204; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_scores
ADD CONSTRAINT fk_rails_0209112204 FOREIGN KEY (patient_id) REFERENCES public.patients(id);


--
-- Name: facility_groups fk_rails_0ba9e6af98; Type: FK CONSTRAINT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -9509,6 +9512,14 @@ ALTER TABLE ONLY public.dr_rai_targets
ADD CONSTRAINT fk_rails_f0398a9ae0 FOREIGN KEY (dr_rai_indicators_id) REFERENCES public.dr_rai_indicators(id);


--
-- Name: patient_attributes fk_rails_fc46ae3757; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY public.patient_attributes
ADD CONSTRAINT fk_rails_fc46ae3757 FOREIGN KEY (patient_id) REFERENCES public.patients(id);


--
-- PostgreSQL database dump complete
--
Expand Down Expand Up @@ -9713,6 +9724,7 @@ INSERT INTO "schema_migrations" (version) VALUES
('20260127150000'),
('20260128094448'),
('20260205110957'),
('20260209112204'),
('20260212195326'),
('20260224063659'),
('20260316093605'),
Expand Down
27 changes: 27 additions & 0 deletions spec/controllers/api/v4/patient_scores_controller_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
require "rails_helper"

describe Api::V4::PatientScoresController, type: :controller do
let(:request_user) { create(:user) }
let(:request_facility_group) { request_user.facility.facility_group }
let(:request_facility) { create(:facility, facility_group: request_facility_group) }
let(:model) { PatientScore }

def create_record(options = {})
facility = create(:facility, facility_group: request_facility_group)
patient = create(:patient, registration_facility: facility)
create(:patient_score, options.merge(patient: patient))
end

def create_record_list(n, options = {})
facility = create(:facility, facility_group_id: request_facility_group.id)
patient = create(:patient, registration_facility_id: facility.id)
create_list(:patient_score, n, options.merge(patient: patient))
end

it_behaves_like "a sync controller that authenticates user requests: sync_to_user"
it_behaves_like "a sync controller that audits the data access: sync_to_user"

describe "GET sync: send data from server to device;" do
it_behaves_like "a working V3 sync controller sending records"
end
end
10 changes: 10 additions & 0 deletions spec/factories/patient_scores.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FactoryBot.define do
factory :patient_score do
id { SecureRandom.uuid }
patient
score_type { "risk_score" }
score_value { 75.50 }
device_created_at { Time.current }
device_updated_at { Time.current }
end
end