renalware-core 2.0.0.pre.rc8 → 2.0.0.pre.rc9
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/renalware/modules/_clinical.scss +73 -0
- data/app/models/concerns/renalware/clinics/most_recent_measurement_scopes.rb.dead +43 -0
- data/app/models/renalware/clinics/clinic_visit.rb +2 -2
- data/app/models/renalware/clinics/current_observations.rb +57 -0
- data/app/models/renalware/letters/letter.rb +5 -1
- data/app/models/renalware/pathology/current_observation_set.rb +6 -0
- data/app/models/renalware/pathology/observation.rb +1 -0
- data/app/models/renalware/pathology/observation_request.rb +3 -0
- data/app/models/renalware/pathology/observations_jsonb_serializer.rb +7 -6
- data/app/models/renalware/pathology/{view_current_observation_results.rb → view_current_observation_results.rb.dead} +0 -0
- data/app/presenters/renalware/clinical/header_presenter.rb +33 -0
- data/app/presenters/renalware/letters/letter_presenter.rb +5 -1
- data/app/presenters/renalware/letters/part_class_filter.rb +36 -0
- data/app/presenters/renalware/renal/clinical_summary_presenter.rb +0 -1
- data/app/presenters/renalware/ukrdc/patient_presenter.rb +8 -2
- data/app/values/renalware/bmi.rb +20 -0
- data/app/views/renalware/admissions/consults/_table.html.slim +1 -1
- data/app/views/renalware/api/ukrdc/patients/_clinic_visit_observation.xml.builder +1 -1
- data/app/views/renalware/api/ukrdc/patients/_lab_orders.xml.builder +3 -3
- data/app/views/renalware/api/ukrdc/patients/_observations.xml.builder +3 -1
- data/app/views/renalware/api/ukrdc/patients/lab_orders/_lab_order.xml.builder +2 -2
- data/app/views/renalware/api/ukrdc/patients/lab_orders/_result_item.xml.builder +7 -2
- data/app/views/renalware/clinical/_header.html.slim +47 -0
- data/app/views/renalware/clinics/clinic_visits/_table.html.slim +0 -1
- data/app/views/renalware/clinics/visits/_table.html.slim +4 -1
- data/app/views/renalware/events/events/_event.html.slim +1 -1
- data/app/views/renalware/events/events/_table.html.slim +3 -0
- data/app/views/renalware/layouts/_patient.html.slim +1 -0
- data/app/views/renalware/letters/letters/_form.html.slim +1 -1
- data/app/views/renalware/letters/letters/_pathology.html.slim +0 -1
- data/app/views/renalware/mdm_patients/_table.html.slim +1 -1
- data/app/views/renalware/pathology/_navigation.html.slim +1 -1
- data/app/views/renalware/patients/patients/_table.html.slim +2 -2
- data/config/initializers/core_extensions.rb +1 -0
- data/config/initializers/inflections.rb +1 -0
- data/config/locales/renalware/clinical/allergies.en.yml +0 -2
- data/config/locales/renalware/clinical/{dry_weight.yml → dry_weight.en.yml} +0 -0
- data/config/locales/renalware/clinical/header.en.yml +14 -0
- data/db/functions/audit_view_as_json_v01.sql +25 -0
- data/db/functions/count_estimate_v01.sql +20 -0
- data/db/functions/generate_patient_secure_id_v01.sql +21 -0
- data/db/functions/generate_secure_id_v01.sql +18 -0
- data/db/functions/import_gps_csv_v01.sql +129 -0
- data/db/functions/import_practice_memberships_csv_v01.sql +48 -0
- data/db/functions/import_practices_csv_v01.sql +109 -0
- data/db/functions/preprocess_hl7_message_v01.sql +29 -0
- data/db/functions/preprocess_hl7_message_v02.sql +31 -0
- data/db/functions/refresh_all_matierialized_views_v01.sql +32 -0
- data/db/functions/refresh_current_observation_set_v01.sql +39 -0
- data/db/functions/update_current_observation_set_from_trigger_v01.sql +88 -0
- data/db/functions/update_current_observation_set_from_trigger_v02.sql +90 -0
- data/db/migrate/20161124152732_add_deleted_at_to_patient_bookmarks.rb +1 -1
- data/db/migrate/20170608135553_create_functions_to_generate_secure_patient_id.rb +4 -48
- data/db/migrate/20170705090219_create_refresh_all_materialized_views_fn.rb +2 -37
- data/db/migrate/20170707110155_rename_access_plans_to_access_plan_types.rb +1 -1
- data/db/migrate/20170831142819_enable_crosstab_extension.rb +2 -2
- data/db/migrate/20170911133224_add_type_to_messaging_messages.rb +1 -1
- data/db/migrate/20170915115228_add_schedule_diurnal_period_id_to_hd_profiles.rb +1 -1
- data/db/migrate/20171013145849_set_patients_secure_id.rb +1 -1
- data/db/migrate/20171101121130_create_function_to_render_audit_view_as_json.rb +2 -29
- data/db/migrate/20171127092158_create_function_to_import_practices.rb +4 -118
- data/db/migrate/20171127092359_create_fn_to_insert_gps.rb +3 -139
- data/db/migrate/20171206140738_create_fn_to_load_practice_memberships_csv.rb +2 -53
- data/db/migrate/20171213111513_create_fn_to_refresh_current_obs.rb +2 -43
- data/db/migrate/20171214141335_create_trigger_to_update_current_observation_sets.rb +3 -101
- data/db/migrate/20180119121243_create_trigger_to_preprocess_hl7_msg.rb +2 -41
- data/db/migrate/20180121115246_add_include_pathology_in_letter_to_letters_letterheads.rb +5 -0
- data/db/migrate/20180125201356_make_obs_set_trigger_change_updated_at.rb +2 -184
- data/db/migrate/20180130165803_add_deleted_at_indexes.rb +16 -0
- data/db/migrate/20180201090444_add_created_at_to_delayed_jobs_in_hl7_trig_fn.rb +9 -0
- data/db/migrate/20180207082540_create_count_estimate_function.rb +9 -0
- data/db/triggers/feed_messages_preprocessing_trigger_v01.sql +5 -0
- data/db/triggers/update_current_observation_set_trigger_v01.sql +7 -0
- data/lib/core_extensions/active_record/migration_helpers.rb +43 -0
- data/lib/renalware/engine.rb +6 -4
- data/lib/renalware/version.rb +1 -1
- data/spec/factories/pathology/observation_descriptions.rb +7 -1
- data/spec/support/shared_examples/accountable_examples.rb +6 -0
- data/spec/support/shared_examples/supersedable_examples.rb +12 -0
- metadata +33 -10
- data/app/models/renalware/letters/delivery/deliver_letter.rb.dead +0 -41
- data/app/models/renalware/pathology/current_key_observation_set.rb.dead +0 -10
- data/app/models/renalware/pathology/update_current_observations.rb.dead +0 -25
- data/app/presenters/renalware/pathology/current_observation_results.dead/presenter.rb.dead +0 -54
- data/app/views/renalware/letters/formatted_letters/show.rtf.slim.ol +0 -1
- data/app/views/renalware/patients/_prescriptions.html.slim.dead +0 -23
@@ -0,0 +1,48 @@
|
|
1
|
+
CREATE OR REPLACE FUNCTION renalware.import_practice_memberships_csv(file text) RETURNS void
|
2
|
+
AS $$
|
3
|
+
BEGIN
|
4
|
+
|
5
|
+
DROP TABLE IF EXISTS memberships_via_copy;
|
6
|
+
CREATE TEMP TABLE copied_memberships (
|
7
|
+
gp_code text NOT NULL,
|
8
|
+
practice_code text NOT NULL,
|
9
|
+
unused3 text,
|
10
|
+
unused4 text,
|
11
|
+
unused5 text,
|
12
|
+
unused7 text
|
13
|
+
);
|
14
|
+
|
15
|
+
-- Import the CSV file into copied_memberships - note there is no CSV header in this file
|
16
|
+
EXECUTE format ('COPY copied_memberships FROM %L DELIMITER %L CSV ', file, ',');
|
17
|
+
|
18
|
+
DROP TABLE IF EXISTS tmp_memberships;
|
19
|
+
CREATE TEMP TABLE tmp_memberships AS
|
20
|
+
SELECT
|
21
|
+
gp_code,
|
22
|
+
practice_code,
|
23
|
+
patient_primary_care_physicians.id primary_care_physician_id,
|
24
|
+
patient_practices.id as practice_id
|
25
|
+
from copied_memberships
|
26
|
+
INNER JOIN patient_practices on patient_practices.code = practice_code
|
27
|
+
INNER JOIN patient_primary_care_physicians on patient_primary_care_physicians.code = gp_code;
|
28
|
+
|
29
|
+
-- Insert any new memberships, ignoring any conflicts where the
|
30
|
+
-- practice_id + primary_care_physician_id already exists
|
31
|
+
INSERT INTO renalware.patient_practice_memberships
|
32
|
+
(practice_id, primary_care_physician_id, created_at, updated_at)
|
33
|
+
SELECT
|
34
|
+
practice_id,
|
35
|
+
primary_care_physician_id,
|
36
|
+
CURRENT_TIMESTAMP,
|
37
|
+
CURRENT_TIMESTAMP
|
38
|
+
FROM tmp_memberships
|
39
|
+
ON CONFLICT (practice_id, primary_care_physician_id) DO NOTHING;
|
40
|
+
|
41
|
+
-- Mark as deleted any memberships not in the latest uploaded data set - ie those gps have retired or moved on
|
42
|
+
UPDATE patient_practice_memberships mem
|
43
|
+
SET deleted_at = CURRENT_TIMESTAMP
|
44
|
+
WHERE NOT EXISTS (select 1 FROM tmp_memberships tmem
|
45
|
+
WHERE tmem.practice_id = mem.practice_id AND tmem.primary_care_physician_id = mem.primary_care_physician_id);
|
46
|
+
|
47
|
+
END;
|
48
|
+
$$ LANGUAGE plpgsql;
|
@@ -0,0 +1,109 @@
|
|
1
|
+
CREATE OR REPLACE FUNCTION renalware.import_practices_csv(file text) RETURNS void AS $$
|
2
|
+
BEGIN
|
3
|
+
/*
|
4
|
+
Imports a practices.csv file created by parsing out an HSCOrgRefData_Full_xxxxx.xml file.
|
5
|
+
*/
|
6
|
+
DROP TABLE IF EXISTS tmp_practices;
|
7
|
+
|
8
|
+
CREATE TEMP TABLE tmp_practices (
|
9
|
+
code text NOT NULL,
|
10
|
+
name text NOT NULL,
|
11
|
+
tel text,
|
12
|
+
street_1 text,
|
13
|
+
street_2 text,
|
14
|
+
street_3 text,
|
15
|
+
town text,
|
16
|
+
county text,
|
17
|
+
postcode text NOT NULL,
|
18
|
+
region text,
|
19
|
+
country_id integer,
|
20
|
+
active text NOT NULL,
|
21
|
+
CONSTRAINT tmp_practices_pkey PRIMARY KEY (code)
|
22
|
+
);
|
23
|
+
|
24
|
+
/* Import the CSV file into tmp_practices, ignoring the first row which is a header */
|
25
|
+
EXECUTE format ('COPY tmp_practices FROM %L DELIMITER %L CSV HEADER', file, ',');
|
26
|
+
|
27
|
+
/* Upsert practices */
|
28
|
+
WITH data(
|
29
|
+
code,
|
30
|
+
name,
|
31
|
+
telephone,
|
32
|
+
street_1,
|
33
|
+
street_2,
|
34
|
+
street_3,
|
35
|
+
town,
|
36
|
+
county,
|
37
|
+
postcode,
|
38
|
+
region,
|
39
|
+
country_id,
|
40
|
+
active)
|
41
|
+
AS (select * from tmp_practices)
|
42
|
+
, practice_changes AS (
|
43
|
+
INSERT INTO patient_practices (code, name, telephone, created_at, updated_at)
|
44
|
+
SELECT code, name, telephone, clock_timestamp(), clock_timestamp()
|
45
|
+
FROM data
|
46
|
+
ON CONFLICT (code) DO UPDATE
|
47
|
+
SET
|
48
|
+
name = excluded.name,
|
49
|
+
telephone = excluded.telephone,
|
50
|
+
updated_at = excluded.updated_at
|
51
|
+
RETURNING code, id
|
52
|
+
)
|
53
|
+
|
54
|
+
/* Upsert practice addresses */
|
55
|
+
INSERT INTO addresses (
|
56
|
+
addressable_type,
|
57
|
+
addressable_id,
|
58
|
+
street_1,
|
59
|
+
street_2,
|
60
|
+
street_3,
|
61
|
+
town,
|
62
|
+
county,
|
63
|
+
postcode,
|
64
|
+
region,
|
65
|
+
country_id,
|
66
|
+
created_at,
|
67
|
+
updated_at)
|
68
|
+
SELECT
|
69
|
+
'Renalware::Patients::Practice',
|
70
|
+
practice_changes.id,
|
71
|
+
street_1,
|
72
|
+
street_2,
|
73
|
+
street_3,
|
74
|
+
town,
|
75
|
+
county,
|
76
|
+
postcode,
|
77
|
+
region,
|
78
|
+
country_id,
|
79
|
+
CURRENT_TIMESTAMP,
|
80
|
+
CURRENT_TIMESTAMP
|
81
|
+
FROM data join practice_changes using(code)
|
82
|
+
ON CONFLICT (addressable_type, addressable_id) DO UPDATE
|
83
|
+
SET
|
84
|
+
street_1 = excluded.street_1,
|
85
|
+
street_2 = excluded.street_2,
|
86
|
+
street_3 = excluded.street_3,
|
87
|
+
town = excluded.town,
|
88
|
+
county = excluded.county,
|
89
|
+
postcode = excluded.postcode,
|
90
|
+
region = excluded.region,
|
91
|
+
country_id = excluded.country_id,
|
92
|
+
updated_at = clock_timestamp();
|
93
|
+
|
94
|
+
/* Update the deleted_at column of any practices which do not have an Active status_code */
|
95
|
+
UPDATE patient_practices AS p
|
96
|
+
SET deleted_at = CURRENT_TIMESTAMP
|
97
|
+
FROM tmp_practices AS tp
|
98
|
+
WHERE p.code = tp.code AND tp.active = 'false';
|
99
|
+
|
100
|
+
/* Set deleted_at tp NULL for active practices */
|
101
|
+
UPDATE patient_practices AS p
|
102
|
+
SET deleted_at = NULL
|
103
|
+
FROM tmp_practices AS tp
|
104
|
+
WHERE p.code = tp.code AND tp.active != 'false';
|
105
|
+
|
106
|
+
DROP TABLE tmp_practices;
|
107
|
+
|
108
|
+
END;
|
109
|
+
$$ LANGUAGE plpgsql;
|
@@ -0,0 +1,29 @@
|
|
1
|
+
/* Create a function for the trigger to call */
|
2
|
+
CREATE OR REPLACE FUNCTION preprocess_hl7_message() RETURNS trigger AS
|
3
|
+
$body$
|
4
|
+
BEGIN
|
5
|
+
/*
|
6
|
+
Mirth inserts a row into delayed job when a new HL7 message needs to be processed by Renalware.
|
7
|
+
The SQL it uses looks like this:
|
8
|
+
insert into renalware.delayed_jobs (handler, run_at)
|
9
|
+
values(E'--- !ruby/struct:FeedJob\nraw_message: |\n ' || REPLACE(${message.rawData},E'\r',E'\n '), NOW());
|
10
|
+
This works unless there is a 10^12 value in the unit of measurement segment for an OBX (e.g.
|
11
|
+
for WBC or HGB). Then Mirth encodes the ^ as \S\ because ^ is a significant character in Mirth
|
12
|
+
(field separator). Unfortunately this creates the combination
|
13
|
+
10\S\12 and S\12 is converted to \n when the handler's payload is loaded in by the delayed_job worker.
|
14
|
+
To get around this we need to convert instances of \S\ with another escape sequence eg «
|
15
|
+
and manually map this back to a ^ in the job handler ruby code.
|
16
|
+
|
17
|
+
So here, if this delayed_job is destined to be picked up by a Feed job handler
|
18
|
+
make sure we convert the Mirth escape sequence \S\ to \\S\\
|
19
|
+
*/
|
20
|
+
IF position('Feed' in NEW.handler) > 0 THEN
|
21
|
+
NEW.handler = replace(NEW.handler, E'\\S\\', E'\\\\S\\\\');
|
22
|
+
END IF;
|
23
|
+
|
24
|
+
RETURN NEW;
|
25
|
+
END
|
26
|
+
|
27
|
+
$body$
|
28
|
+
LANGUAGE plpgsql;
|
29
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
/* Create a function for the trigger to call */
|
2
|
+
CREATE OR REPLACE FUNCTION preprocess_hl7_message() RETURNS trigger AS
|
3
|
+
$body$
|
4
|
+
BEGIN
|
5
|
+
/*
|
6
|
+
Mirth inserts a row into delayed job when a new HL7 message needs to be processed by Renalware.
|
7
|
+
The SQL it uses looks like this:
|
8
|
+
insert into renalware.delayed_jobs (handler, run_at)
|
9
|
+
values(E'--- !ruby/struct:FeedJob\nraw_message: |\n ' || REPLACE(${message.rawData},E'\r',E'\n '), NOW());
|
10
|
+
This works unless there is a 10^12 value in the unit of measurement segment for an OBX (e.g.
|
11
|
+
for WBC or HGB). Then Mirth encodes the ^ as \S\ because ^ is a significant character in Mirth
|
12
|
+
(field separator). Unfortunately this creates the combination
|
13
|
+
10\S\12 and S\12 is converted to \n when the handler's payload is loaded in by the delayed_job worker.
|
14
|
+
To get around this we need to convert instances of \S\ with another escape sequence eg «
|
15
|
+
and manually map this back to a ^ in the job handler ruby code.
|
16
|
+
|
17
|
+
So here, if this delayed_job is destined to be picked up by a Feed job handler
|
18
|
+
make sure we convert the Mirth escape sequence \S\ to \\S\\
|
19
|
+
*/
|
20
|
+
IF position('Feed' in NEW.handler) > 0 THEN
|
21
|
+
NEW.handler = replace(NEW.handler, E'\\S\\', E'\\\\S\\\\');
|
22
|
+
NEW.created_at = now();
|
23
|
+
NEW.updated_at = now();
|
24
|
+
END IF;
|
25
|
+
|
26
|
+
RETURN NEW;
|
27
|
+
END
|
28
|
+
|
29
|
+
$body$
|
30
|
+
LANGUAGE plpgsql;
|
31
|
+
|
@@ -0,0 +1,32 @@
|
|
1
|
+
CREATE OR REPLACE FUNCTION
|
2
|
+
refresh_all_matierialized_views(_schema TEXT DEFAULT '*', _concurrently BOOLEAN DEFAULT false)
|
3
|
+
/*
|
4
|
+
Function to refresh all materialized views in all or a specified schema,
|
5
|
+
optionally concurrently
|
6
|
+
From https://github.com/frankhommers/RefreshAllMaterializedViews
|
7
|
+
*/
|
8
|
+
RETURNS INT AS $$
|
9
|
+
DECLARE
|
10
|
+
r RECORD;
|
11
|
+
BEGIN
|
12
|
+
RAISE NOTICE 'Refreshing materialized view(s) in % %',
|
13
|
+
CASE WHEN _schema = '*' THEN 'all schemas'
|
14
|
+
ELSE 'schema "'|| _schema || '"'
|
15
|
+
END,
|
16
|
+
CASE WHEN _concurrently
|
17
|
+
THEN 'concurrently'
|
18
|
+
ELSE '' END;
|
19
|
+
IF pg_is_in_recovery() THEN
|
20
|
+
RETURN 0;
|
21
|
+
ELSE
|
22
|
+
FOR r IN SELECT schemaname,
|
23
|
+
matviewname FROM pg_matviews WHERE schemaname = _schema OR _schema = '*'
|
24
|
+
LOOP
|
25
|
+
RAISE NOTICE 'Refreshing materialized view "%"."%"', r.schemaname, r.matviewname;
|
26
|
+
EXECUTE 'REFRESH MATERIALIZED VIEW ' || CASE WHEN _concurrently THEN 'CONCURRENTLY '
|
27
|
+
ELSE '' END || '"' || r.schemaname || '"."' || r.matviewname || '"';
|
28
|
+
END LOOP;
|
29
|
+
END IF;
|
30
|
+
RETURN 1;
|
31
|
+
END
|
32
|
+
$$ LANGUAGE plpgsql;
|
@@ -0,0 +1,39 @@
|
|
1
|
+
CREATE OR REPLACE FUNCTION refresh_current_observation_set(a_patient_id integer)
|
2
|
+
-- Function to update the pathology_current_observation_set for a patient.
|
3
|
+
-- It stores the most recent results into the jsonb hash on that table.
|
4
|
+
-- To run for all patients, use
|
5
|
+
-- select refresh_current_observation_set(id) from patients;
|
6
|
+
--
|
7
|
+
RETURNS integer
|
8
|
+
LANGUAGE 'plpgsql'
|
9
|
+
as $$
|
10
|
+
BEGIN
|
11
|
+
with current_patient_obs as(
|
12
|
+
select
|
13
|
+
DISTINCT ON (p.id, obxd.id)
|
14
|
+
p.id as patient_id,
|
15
|
+
obxd.code,
|
16
|
+
json_build_object('result',(obx.result),'observed_at',obx.observed_at) as value
|
17
|
+
from patients p
|
18
|
+
inner join pathology_observation_requests obr on obr.patient_id = p.id
|
19
|
+
inner join pathology_observations obx on obx.request_id = obr.id
|
20
|
+
inner join pathology_observation_descriptions obxd on obx.description_id = obxd.id
|
21
|
+
where p.id = a_patient_id
|
22
|
+
order by p.id, obxd.id, obx.observed_at desc
|
23
|
+
),
|
24
|
+
current_patient_obs_as_jsonb as (
|
25
|
+
select patient_id,
|
26
|
+
jsonb_object_agg(code, value) as values,
|
27
|
+
CURRENT_TIMESTAMP,
|
28
|
+
CURRENT_TIMESTAMP
|
29
|
+
from current_patient_obs
|
30
|
+
group by patient_id order by patient_id
|
31
|
+
)
|
32
|
+
insert into pathology_current_observation_sets (patient_id, values, created_at, updated_at)
|
33
|
+
select * from current_patient_obs_as_jsonb
|
34
|
+
ON conflict (patient_id)
|
35
|
+
DO UPDATE
|
36
|
+
SET values = excluded.values, updated_at = excluded.updated_at;
|
37
|
+
RETURN a_patient_id;
|
38
|
+
END
|
39
|
+
$$;
|
@@ -0,0 +1,88 @@
|
|
1
|
+
CREATE OR REPLACE FUNCTION update_current_observation_set_from_trigger() RETURNS TRIGGER AS $body$
|
2
|
+
-- TC 14/12/2017 v01
|
3
|
+
-- This function is called by a trigger when a row is inserted or updated in
|
4
|
+
-- pathology_observations. Its purpose is to keep current_observation_sets up to date
|
5
|
+
-- with the latest observations for any patient.
|
6
|
+
-- The current_observation_sets table maintains a jsonb hash into which we insert or replace
|
7
|
+
-- the observation, keyed by OBX code.
|
8
|
+
-- e.g. .. {"HGB": { "result": 123.1, "observed_at": '2017-12-12-01:01:01'}, ..
|
9
|
+
DECLARE
|
10
|
+
a_patient_id bigint;
|
11
|
+
a_code text;
|
12
|
+
current_observed_at timestamp;
|
13
|
+
current_result text;
|
14
|
+
new_observed_at timestamp;
|
15
|
+
BEGIN
|
16
|
+
RAISE NOTICE 'TRIGGER called on %',TG_TABLE_NAME ;
|
17
|
+
|
18
|
+
/*
|
19
|
+
If inserting or updating, we _could_ assume the last observation to be inserted is
|
20
|
+
the most 'recent' one (with the latest observed_at date).
|
21
|
+
However the order of incoming messages is not guaranteed, so we have two options:
|
22
|
+
1. Refresh the entire current_observation_set for the patient
|
23
|
+
2. Check the current observed_at date in the jsonb and only update if we have a more
|
24
|
+
recent one
|
25
|
+
We have gone for 2.
|
26
|
+
*/
|
27
|
+
|
28
|
+
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
29
|
+
|
30
|
+
-- Note we could re-generate the entire current pathology for the patient using
|
31
|
+
-- select refresh_current_observation_set(a_patient_id);
|
32
|
+
-- which is safer but uses more resources, so avoiding this for now.
|
33
|
+
|
34
|
+
-- Find and store patient_id into local variable
|
35
|
+
select request.patient_id into a_patient_id
|
36
|
+
from pathology_observation_requests request
|
37
|
+
where request.id = NEW.request_id;
|
38
|
+
|
39
|
+
-- Find and store the obx code into local variable
|
40
|
+
select description.code into a_code
|
41
|
+
from pathology_observation_descriptions description
|
42
|
+
where description.id = NEW.description_id;
|
43
|
+
|
44
|
+
-- Important! Create the observation_set if it doesn exist yet
|
45
|
+
-- ignore the error id the row already exists
|
46
|
+
insert into pathology_current_observation_sets (patient_id)
|
47
|
+
values (a_patient_id)
|
48
|
+
ON CONFLICT DO NOTHING;
|
49
|
+
|
50
|
+
-- We are going to compare the current and new observed_at dates
|
51
|
+
-- so need to cast them to a timestamp
|
52
|
+
select cast(New.observed_at as timestamp) into new_observed_at;
|
53
|
+
|
54
|
+
-- Get the most recent date and value for this observation
|
55
|
+
-- and store to variables.
|
56
|
+
select
|
57
|
+
cast(values -> a_code ->> 'observed_at' as timestamp),
|
58
|
+
values -> a_code ->> 'result'
|
59
|
+
into current_observed_at, current_result from
|
60
|
+
pathology_current_observation_sets
|
61
|
+
where patient_id = a_patient_id;
|
62
|
+
|
63
|
+
-- Output some info to helps us debug. This can be removed later.
|
64
|
+
RAISE NOTICE ' Request id % Patient id % Code %', NEW.request_id, a_patient_id, a_code;
|
65
|
+
RAISE NOTICE ' Last %: % at %', a_code, current_result, current_observed_at;
|
66
|
+
RAISE NOTICE ' New %: % at %', a_code, NEW.result, new_observed_at;
|
67
|
+
|
68
|
+
IF current_observed_at IS NULL OR new_observed_at >= current_observed_at THEN
|
69
|
+
-- The new pathology_observation row contain a more recent result that the old one.
|
70
|
+
-- (note there may not be an old one if the patient has neve had this obs before).
|
71
|
+
|
72
|
+
RAISE NOTICE ' Updating pathology_current_observation_sets..';
|
73
|
+
|
74
|
+
-- Update the values jsonb column with the new hash for this code, e.g.
|
75
|
+
-- .. {"HGB": { "result": 123.1, "observed_at": '2017-12-12-01:01:01'}, ..
|
76
|
+
-- Note the `set values` below actually reads in the jsonb, updates it,
|
77
|
+
-- and wites the whole thing back.
|
78
|
+
update pathology_current_observation_sets
|
79
|
+
set values = jsonb_set(
|
80
|
+
values,
|
81
|
+
('{'||a_code||'}')::text[], -- defined in the fn path::text[]
|
82
|
+
jsonb_build_object('result', NEW.result, 'observed_at', new_observed_at),
|
83
|
+
true)
|
84
|
+
where patient_id = a_patient_id;
|
85
|
+
END IF;
|
86
|
+
END IF;
|
87
|
+
RETURN NULL ;
|
88
|
+
END $body$ LANGUAGE plpgsql VOLATILE COST 100;
|
@@ -0,0 +1,90 @@
|
|
1
|
+
CREATE OR REPLACE FUNCTION update_current_observation_set_from_trigger() RETURNS TRIGGER AS $body$
|
2
|
+
-- TC 14/12/2017 v02
|
3
|
+
-- This function is called by a trigger when a row is inserted or updated in
|
4
|
+
-- pathology_observations. Its purpose is to keep current_observation_sets up to date
|
5
|
+
-- with the latest observations for any patient.
|
6
|
+
-- The current_observation_sets table maintains a jsonb hash into which we insert or replace
|
7
|
+
-- the observation, keyed by OBX code.
|
8
|
+
-- e.g. .. {"HGB": { "result": 123.1, "observed_at": '2017-12-12-01:01:01'}, ..
|
9
|
+
DECLARE
|
10
|
+
a_patient_id bigint;
|
11
|
+
a_code text;
|
12
|
+
current_observed_at timestamp;
|
13
|
+
current_result text;
|
14
|
+
new_observed_at timestamp;
|
15
|
+
BEGIN
|
16
|
+
RAISE NOTICE 'TRIGGER called on %',TG_TABLE_NAME ;
|
17
|
+
|
18
|
+
/*
|
19
|
+
If inserting or updating, we _could_ assume the last observation to be inserted is
|
20
|
+
the most 'recent' one (with the latest observed_at date).
|
21
|
+
However the order of incoming messages is not guaranteed, so we have two options:
|
22
|
+
1. Refresh the entire current_observation_set for the patient
|
23
|
+
2. Check the current observed_at date in the jsonb and only update if we have a more
|
24
|
+
recent one
|
25
|
+
We have gone for 2.
|
26
|
+
*/
|
27
|
+
|
28
|
+
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
29
|
+
|
30
|
+
-- Note we could re-generate the entire current pathology for the patient using
|
31
|
+
-- select refresh_current_observation_set(a_patient_id);
|
32
|
+
-- which is safer but uses more resources, so avoiding this for now.
|
33
|
+
|
34
|
+
-- Find and store patient_id into local variable
|
35
|
+
select request.patient_id into a_patient_id
|
36
|
+
from pathology_observation_requests request
|
37
|
+
where request.id = NEW.request_id;
|
38
|
+
|
39
|
+
-- Find and store the obx code into local variable
|
40
|
+
select description.code into a_code
|
41
|
+
from pathology_observation_descriptions description
|
42
|
+
where description.id = NEW.description_id;
|
43
|
+
|
44
|
+
-- Important! Create the observation_set if it doesn't exist yet
|
45
|
+
-- ignore the error if the row already exists
|
46
|
+
insert into pathology_current_observation_sets (patient_id)
|
47
|
+
values (a_patient_id)
|
48
|
+
ON CONFLICT DO NOTHING;
|
49
|
+
|
50
|
+
-- We are going to compare the current and new observed_at dates
|
51
|
+
-- so need to cast them to a timestamp
|
52
|
+
select cast(New.observed_at as timestamp) into new_observed_at;
|
53
|
+
|
54
|
+
-- Get the most recent date and value for this observation
|
55
|
+
-- and store to variables.
|
56
|
+
select
|
57
|
+
cast(values -> a_code ->> 'observed_at' as timestamp),
|
58
|
+
values -> a_code ->> 'result'
|
59
|
+
into current_observed_at, current_result from
|
60
|
+
pathology_current_observation_sets
|
61
|
+
where patient_id = a_patient_id;
|
62
|
+
|
63
|
+
-- Output some info to helps us debug. This can be removed later.
|
64
|
+
RAISE NOTICE ' Request id % Patient id % Code %', NEW.request_id, a_patient_id, a_code;
|
65
|
+
RAISE NOTICE ' Last %: % at %', a_code, current_result, current_observed_at;
|
66
|
+
RAISE NOTICE ' New %: % at %', a_code, NEW.result, new_observed_at;
|
67
|
+
|
68
|
+
IF current_observed_at IS NULL OR new_observed_at >= current_observed_at THEN
|
69
|
+
-- The new pathology_observation row contain a more recent result that the old one.
|
70
|
+
-- (note there may not be an old one if the patient has neve had this obs before).
|
71
|
+
|
72
|
+
RAISE NOTICE ' Updating pathology_current_observation_sets..';
|
73
|
+
|
74
|
+
-- Update the values jsonb column with the new hash for this code, e.g.
|
75
|
+
-- .. {"HGB": { "result": 123.1, "observed_at": '2017-12-12-01:01:01'}, ..
|
76
|
+
-- Note the `set values` below actually reads in the jsonb, updates it,
|
77
|
+
-- and wites the whole thing back.
|
78
|
+
update pathology_current_observation_sets
|
79
|
+
set updated_at = CURRENT_TIMESTAMP,
|
80
|
+
values = jsonb_set(
|
81
|
+
values,
|
82
|
+
('{'||a_code||'}')::text[], -- defined in the fn path::text[]
|
83
|
+
jsonb_build_object('result', NEW.result, 'observed_at', new_observed_at),
|
84
|
+
true)
|
85
|
+
where patient_id = a_patient_id;
|
86
|
+
END IF;
|
87
|
+
END IF;
|
88
|
+
RETURN NULL ;
|
89
|
+
END $body$ LANGUAGE plpgsql VOLATILE COST 100;
|
90
|
+
-- End function
|