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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/renalware/modules/_clinical.scss +73 -0
  3. data/app/models/concerns/renalware/clinics/most_recent_measurement_scopes.rb.dead +43 -0
  4. data/app/models/renalware/clinics/clinic_visit.rb +2 -2
  5. data/app/models/renalware/clinics/current_observations.rb +57 -0
  6. data/app/models/renalware/letters/letter.rb +5 -1
  7. data/app/models/renalware/pathology/current_observation_set.rb +6 -0
  8. data/app/models/renalware/pathology/observation.rb +1 -0
  9. data/app/models/renalware/pathology/observation_request.rb +3 -0
  10. data/app/models/renalware/pathology/observations_jsonb_serializer.rb +7 -6
  11. data/app/models/renalware/pathology/{view_current_observation_results.rb → view_current_observation_results.rb.dead} +0 -0
  12. data/app/presenters/renalware/clinical/header_presenter.rb +33 -0
  13. data/app/presenters/renalware/letters/letter_presenter.rb +5 -1
  14. data/app/presenters/renalware/letters/part_class_filter.rb +36 -0
  15. data/app/presenters/renalware/renal/clinical_summary_presenter.rb +0 -1
  16. data/app/presenters/renalware/ukrdc/patient_presenter.rb +8 -2
  17. data/app/values/renalware/bmi.rb +20 -0
  18. data/app/views/renalware/admissions/consults/_table.html.slim +1 -1
  19. data/app/views/renalware/api/ukrdc/patients/_clinic_visit_observation.xml.builder +1 -1
  20. data/app/views/renalware/api/ukrdc/patients/_lab_orders.xml.builder +3 -3
  21. data/app/views/renalware/api/ukrdc/patients/_observations.xml.builder +3 -1
  22. data/app/views/renalware/api/ukrdc/patients/lab_orders/_lab_order.xml.builder +2 -2
  23. data/app/views/renalware/api/ukrdc/patients/lab_orders/_result_item.xml.builder +7 -2
  24. data/app/views/renalware/clinical/_header.html.slim +47 -0
  25. data/app/views/renalware/clinics/clinic_visits/_table.html.slim +0 -1
  26. data/app/views/renalware/clinics/visits/_table.html.slim +4 -1
  27. data/app/views/renalware/events/events/_event.html.slim +1 -1
  28. data/app/views/renalware/events/events/_table.html.slim +3 -0
  29. data/app/views/renalware/layouts/_patient.html.slim +1 -0
  30. data/app/views/renalware/letters/letters/_form.html.slim +1 -1
  31. data/app/views/renalware/letters/letters/_pathology.html.slim +0 -1
  32. data/app/views/renalware/mdm_patients/_table.html.slim +1 -1
  33. data/app/views/renalware/pathology/_navigation.html.slim +1 -1
  34. data/app/views/renalware/patients/patients/_table.html.slim +2 -2
  35. data/config/initializers/core_extensions.rb +1 -0
  36. data/config/initializers/inflections.rb +1 -0
  37. data/config/locales/renalware/clinical/allergies.en.yml +0 -2
  38. data/config/locales/renalware/clinical/{dry_weight.yml → dry_weight.en.yml} +0 -0
  39. data/config/locales/renalware/clinical/header.en.yml +14 -0
  40. data/db/functions/audit_view_as_json_v01.sql +25 -0
  41. data/db/functions/count_estimate_v01.sql +20 -0
  42. data/db/functions/generate_patient_secure_id_v01.sql +21 -0
  43. data/db/functions/generate_secure_id_v01.sql +18 -0
  44. data/db/functions/import_gps_csv_v01.sql +129 -0
  45. data/db/functions/import_practice_memberships_csv_v01.sql +48 -0
  46. data/db/functions/import_practices_csv_v01.sql +109 -0
  47. data/db/functions/preprocess_hl7_message_v01.sql +29 -0
  48. data/db/functions/preprocess_hl7_message_v02.sql +31 -0
  49. data/db/functions/refresh_all_matierialized_views_v01.sql +32 -0
  50. data/db/functions/refresh_current_observation_set_v01.sql +39 -0
  51. data/db/functions/update_current_observation_set_from_trigger_v01.sql +88 -0
  52. data/db/functions/update_current_observation_set_from_trigger_v02.sql +90 -0
  53. data/db/migrate/20161124152732_add_deleted_at_to_patient_bookmarks.rb +1 -1
  54. data/db/migrate/20170608135553_create_functions_to_generate_secure_patient_id.rb +4 -48
  55. data/db/migrate/20170705090219_create_refresh_all_materialized_views_fn.rb +2 -37
  56. data/db/migrate/20170707110155_rename_access_plans_to_access_plan_types.rb +1 -1
  57. data/db/migrate/20170831142819_enable_crosstab_extension.rb +2 -2
  58. data/db/migrate/20170911133224_add_type_to_messaging_messages.rb +1 -1
  59. data/db/migrate/20170915115228_add_schedule_diurnal_period_id_to_hd_profiles.rb +1 -1
  60. data/db/migrate/20171013145849_set_patients_secure_id.rb +1 -1
  61. data/db/migrate/20171101121130_create_function_to_render_audit_view_as_json.rb +2 -29
  62. data/db/migrate/20171127092158_create_function_to_import_practices.rb +4 -118
  63. data/db/migrate/20171127092359_create_fn_to_insert_gps.rb +3 -139
  64. data/db/migrate/20171206140738_create_fn_to_load_practice_memberships_csv.rb +2 -53
  65. data/db/migrate/20171213111513_create_fn_to_refresh_current_obs.rb +2 -43
  66. data/db/migrate/20171214141335_create_trigger_to_update_current_observation_sets.rb +3 -101
  67. data/db/migrate/20180119121243_create_trigger_to_preprocess_hl7_msg.rb +2 -41
  68. data/db/migrate/20180121115246_add_include_pathology_in_letter_to_letters_letterheads.rb +5 -0
  69. data/db/migrate/20180125201356_make_obs_set_trigger_change_updated_at.rb +2 -184
  70. data/db/migrate/20180130165803_add_deleted_at_indexes.rb +16 -0
  71. data/db/migrate/20180201090444_add_created_at_to_delayed_jobs_in_hl7_trig_fn.rb +9 -0
  72. data/db/migrate/20180207082540_create_count_estimate_function.rb +9 -0
  73. data/db/triggers/feed_messages_preprocessing_trigger_v01.sql +5 -0
  74. data/db/triggers/update_current_observation_set_trigger_v01.sql +7 -0
  75. data/lib/core_extensions/active_record/migration_helpers.rb +43 -0
  76. data/lib/renalware/engine.rb +6 -4
  77. data/lib/renalware/version.rb +1 -1
  78. data/spec/factories/pathology/observation_descriptions.rb +7 -1
  79. data/spec/support/shared_examples/accountable_examples.rb +6 -0
  80. data/spec/support/shared_examples/supersedable_examples.rb +12 -0
  81. metadata +33 -10
  82. data/app/models/renalware/letters/delivery/deliver_letter.rb.dead +0 -41
  83. data/app/models/renalware/pathology/current_key_observation_set.rb.dead +0 -10
  84. data/app/models/renalware/pathology/update_current_observations.rb.dead +0 -25
  85. data/app/presenters/renalware/pathology/current_observation_results.dead/presenter.rb.dead +0 -54
  86. data/app/views/renalware/letters/formatted_letters/show.rtf.slim.ol +0 -1
  87. 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