renalware-core 2.0.28 → 2.0.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/renalware/modules/_clinical.scss +19 -24
- data/app/controllers/renalware/research/studies_controller.rb +4 -1
- data/app/models/renalware/hd/sessions/save_session.rb +8 -1
- data/app/models/renalware/research/study_participant.rb +6 -0
- data/app/views/renalware/research/_alert.html.slim +9 -1
- data/app/views/renalware/research/studies/_form.html.slim +9 -0
- data/app/views/renalware/research/study_participants/_table.html.slim +2 -0
- data/db/functions/pseudo_encrypt_v01.sql +28 -0
- data/db/functions/update_research_study_participants_from_trigger_v01.sql +22 -0
- data/db/migrate/20180605114332_create_pseudo_encrypt_function.rb +9 -0
- data/db/migrate/20180605141806_add_external_id_to_research_study_participants.rb +40 -0
- data/db/migrate/20180605175211_add_application_url_to_research_studies.rb +5 -0
- data/db/triggers/update_research_study_participants_trigger_v01.sql +6 -0
- data/lib/core_extensions/active_record/migration_helpers.rb +21 -4
- data/lib/renalware/version.rb +1 -1
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b29e1fc4d6a1bb7e86fdcf56f18246fd8e39119376366f7017838cedfd4b087f
|
4
|
+
data.tar.gz: d5cd02cdd2dcb4b42cd8c5d0ecc32f7dfce445e45a00adee57556b49adba4ad1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 529f354080faf23959520fbf19b5db5da08e2bbf9cacab2d76f35964c807e43ad509ca2f7259942ca336fff48489224b39ceb4db9d23c789604455d6020114a1
|
7
|
+
data.tar.gz: a5f9c60d939e1c7afde8800f06da5d9d65b61e373d34530a5e2f6cdd2354f2cc86c9d1455193d7dc7a45531204b845cb6f64d11363fabf94c4ca609da166a8cd
|
@@ -49,8 +49,7 @@ article.clinical-allergies {
|
|
49
49
|
}
|
50
50
|
}
|
51
51
|
|
52
|
-
.clinical-header
|
53
|
-
.research-study-participation-alerts {
|
52
|
+
.clinical-header {
|
54
53
|
background-color: $table-border-colour;
|
55
54
|
padding: 0em .2em .2em .6em;
|
56
55
|
|
@@ -125,25 +124,17 @@ article.clinical-allergies {
|
|
125
124
|
}
|
126
125
|
}
|
127
126
|
|
128
|
-
.research-study-participation-alerts {
|
129
|
-
.lozenge {
|
130
|
-
ul {
|
131
|
-
li {
|
132
|
-
background-color: $nhs-pink;
|
133
|
-
color: $white;
|
134
|
-
}
|
135
|
-
}
|
136
|
-
}
|
137
|
-
}
|
138
|
-
|
139
127
|
.patient-alerts {
|
140
|
-
|
128
|
+
color: $white;
|
129
|
+
|
130
|
+
@media print {
|
141
131
|
display: none;
|
142
132
|
}
|
143
133
|
|
144
134
|
&.lozenge {
|
145
135
|
ul {
|
146
136
|
margin: 0;
|
137
|
+
//font-size: 0.9rem;
|
147
138
|
|
148
139
|
li {
|
149
140
|
background-color: $nhs-orange;
|
@@ -157,29 +148,33 @@ article.clinical-allergies {
|
|
157
148
|
background-color: darken($nhs-orange, 5);
|
158
149
|
}
|
159
150
|
|
160
|
-
&:hover,
|
161
|
-
span:hover {
|
162
|
-
color: white
|
163
|
-
}
|
164
|
-
|
165
151
|
&.urgent {
|
166
152
|
background-color: $nhs-red;
|
167
153
|
|
168
154
|
&:hover {
|
169
|
-
background-color: darken($nhs-red,
|
155
|
+
background-color: darken($nhs-red, 7);
|
170
156
|
}
|
171
157
|
}
|
172
158
|
|
173
159
|
&.research-study-participant {
|
174
|
-
background-color: $nhs-green;
|
160
|
+
background-color: darken($nhs-green, 3);
|
161
|
+
color: $white;
|
175
162
|
|
176
163
|
&:hover {
|
177
164
|
background-color: darken($nhs-green, 5);
|
178
165
|
}
|
179
166
|
|
180
167
|
a {
|
168
|
+
color: $white;;
|
169
|
+
border-bottom: 1px dotted $white
|
170
|
+
}
|
171
|
+
|
172
|
+
.date {
|
173
|
+
border-left: 1px solid $mid-grey;
|
174
|
+
}
|
175
|
+
|
176
|
+
span {
|
181
177
|
color: $white;
|
182
|
-
border-bottom: 1px dotted $light-grey;
|
183
178
|
}
|
184
179
|
}
|
185
180
|
|
@@ -187,7 +182,8 @@ article.clinical-allergies {
|
|
187
182
|
padding: 0 .4rem;
|
188
183
|
}
|
189
184
|
|
190
|
-
.body
|
185
|
+
.body,
|
186
|
+
.external_application_link {
|
191
187
|
padding-left: 0;
|
192
188
|
}
|
193
189
|
|
@@ -196,7 +192,6 @@ article.clinical-allergies {
|
|
196
192
|
.actions {
|
197
193
|
font-style: italic;
|
198
194
|
border-left: 1px solid $winter-sky;
|
199
|
-
color: $light-grey;
|
200
195
|
white-space: nowrap;
|
201
196
|
|
202
197
|
.fa {
|
@@ -66,7 +66,10 @@ module Renalware
|
|
66
66
|
def study_params
|
67
67
|
params
|
68
68
|
.require(:research_study)
|
69
|
-
.permit(
|
69
|
+
.permit(
|
70
|
+
:code, :description, :leader, :notes, :started_on, :terminated_on,
|
71
|
+
:application_url
|
72
|
+
)
|
70
73
|
end
|
71
74
|
end
|
72
75
|
end
|
@@ -75,7 +75,7 @@ module Renalware
|
|
75
75
|
session = session.becomes!(Session::Closed)
|
76
76
|
session.profile = patient.hd_profile
|
77
77
|
session.signed_off_at = Time.zone.now
|
78
|
-
session.dry_weight =
|
78
|
+
session.dry_weight = most_recent_dry_weight
|
79
79
|
session
|
80
80
|
end
|
81
81
|
|
@@ -83,6 +83,13 @@ module Renalware
|
|
83
83
|
session_type.constantize
|
84
84
|
end
|
85
85
|
|
86
|
+
def most_recent_dry_weight
|
87
|
+
Renalware::Clinical::DryWeight
|
88
|
+
.for_patient(patient)
|
89
|
+
.order(assessed_on: :desc)
|
90
|
+
.first
|
91
|
+
end
|
92
|
+
|
86
93
|
def lookup_access_type_abbreviation(session)
|
87
94
|
return unless session.document&.respond_to?(:info)
|
88
95
|
access_type = Accesses::Type.find_by(name: session.document.info.access_type)
|
@@ -9,6 +9,7 @@ module Renalware
|
|
9
9
|
acts_as_paranoid
|
10
10
|
validates :participant_id, presence: true, uniqueness: { scope: :study }
|
11
11
|
validates :study, presence: true
|
12
|
+
validates :external_id, uniqueness: true # added by a trigger
|
12
13
|
belongs_to :study, touch: true
|
13
14
|
belongs_to :patient,
|
14
15
|
class_name: "Renalware::Patient",
|
@@ -18,6 +19,11 @@ module Renalware
|
|
18
19
|
def to_s
|
19
20
|
patient&.to_s
|
20
21
|
end
|
22
|
+
|
23
|
+
def external_application_participant_url
|
24
|
+
return if study.application_url.blank?
|
25
|
+
study.application_url.gsub("{external_id}", external_id.to_s)
|
26
|
+
end
|
21
27
|
end
|
22
28
|
end
|
23
29
|
end
|
@@ -2,5 +2,13 @@ li.patient-alert.research-study-participant
|
|
2
2
|
i.fa.fa-warning
|
3
3
|
span.title= link_to(participation.study.code, research_study_participants_path(participation.study))
|
4
4
|
span.body participant
|
5
|
-
span.date= l(participation.joined_on)
|
6
5
|
|
6
|
+
/ A Study might have a url linking to an external study application. In this case show the link
|
7
|
+
/ in the alert.
|
8
|
+
- if participation.external_application_participant_url.present?
|
9
|
+
span.external_application_link
|
10
|
+
= link_to(participation.external_application_participant_url, target: "_blank") do
|
11
|
+
i.fa.fa-external-link
|
12
|
+
| Launch application
|
13
|
+
|
14
|
+
span.date= l(participation.joined_on)
|
@@ -2,6 +2,15 @@
|
|
2
2
|
= f.input :code, wrapper: :horizontal_small
|
3
3
|
= f.input :description, wrapper: :horizontal_large
|
4
4
|
= f.input :leader, wrapper: :horizontal_small
|
5
|
+
= f.input :application_url,
|
6
|
+
wrapper: :horizontal_medium,
|
7
|
+
hint: "If there is an an external application associated with this study " \
|
8
|
+
"you can enter it here and, for participanting patients, the link " \
|
9
|
+
"will appear at the top of patient pages.<br/>" \
|
10
|
+
"Use the format https://my-research-app/participants/{external_id}<br/> if you " \
|
11
|
+
"The {external_id} placeholder will be replaced with " \
|
12
|
+
"research_study_particpant_external_id".html_safe,
|
13
|
+
placeholder: "e.g. https://my-research-app/participants/{external_id}"
|
5
14
|
= f.input :started_on, as: :date_picker, wrapper: :horizontal_datepicker
|
6
15
|
= f.input :terminated_on, as: :date_picker, wrapper: :horizontal_datepicker
|
7
16
|
= f.input :notes, as: :text, wrapper: :horizontal_large, input_html: { rows: 6 }
|
@@ -9,6 +9,7 @@
|
|
9
9
|
th.col-width-reference-no Hosp no
|
10
10
|
th.col-width-tiny Age
|
11
11
|
th Sex
|
12
|
+
th External ID
|
12
13
|
th.col-width-small
|
13
14
|
tbody
|
14
15
|
- participants.each do |participant|
|
@@ -20,6 +21,7 @@
|
|
20
21
|
td= participant.patient.hospital_identifiers
|
21
22
|
td= participant.patient.age
|
22
23
|
td= participant.patient.sex
|
24
|
+
td= participant.external_id
|
23
25
|
td
|
24
26
|
= link_to "Delete",
|
25
27
|
research_study_participant_path(study, participant),
|
@@ -0,0 +1,28 @@
|
|
1
|
+
/*
|
2
|
+
See https://wiki.postgresql.org/wiki/Pseudo_encrypt
|
3
|
+
Encrypt an id eg 101 and generate a unique integer eg
|
4
|
+
select pseudo_encrypt(101); # => 1064847687
|
5
|
+
pseudo_encrypt will always return the same number for any input integer.
|
6
|
+
Useful creating a psuedo-random id that is guaranteed to be unique.
|
7
|
+
Could be used in conjunction with a sequence
|
8
|
+
*/
|
9
|
+
CREATE OR REPLACE FUNCTION renalware.pseudo_encrypt(VALUE int) returns int AS $$
|
10
|
+
DECLARE
|
11
|
+
l1 int;
|
12
|
+
l2 int;
|
13
|
+
r1 int;
|
14
|
+
r2 int;
|
15
|
+
i int:=0;
|
16
|
+
BEGIN
|
17
|
+
l1:= (VALUE >> 16) & 65535;
|
18
|
+
r1:= VALUE & 65535;
|
19
|
+
WHILE i < 3 LOOP
|
20
|
+
l2 := r1;
|
21
|
+
r2 := l1 # ((((1366 * r1 + 150889) % 714025) / 714025.0) * 32767)::int;
|
22
|
+
l1 := l2;
|
23
|
+
r1 := r2;
|
24
|
+
i := i + 1;
|
25
|
+
END LOOP;
|
26
|
+
RETURN ((r1 << 16) + l1);
|
27
|
+
END;
|
28
|
+
$$ LANGUAGE plpgsql strict immutable;
|
@@ -0,0 +1,22 @@
|
|
1
|
+
set SEARCH_PATH=renalware,public;
|
2
|
+
CREATE OR REPLACE FUNCTION renalware.update_research_study_participants_from_trigger() RETURNS TRIGGER AS $body$
|
3
|
+
/*
|
4
|
+
TC 05/06/2018
|
5
|
+
After a participant is added to a study, assign them an external_id, to be used when sending this
|
6
|
+
data for example to an external study application.
|
7
|
+
We use pseudo_encrypt() to generate a random id which is guaranteed to be unique as it is based
|
8
|
+
on the id. Its not the most secure however as, without a secret, the id can be reverse engineered
|
9
|
+
if our pseudo_encrypt sql function open source (which it is). If this is deemed to be a problem
|
10
|
+
(our intention at this point is rudimentary obfuscation), a hospital can override replace this
|
11
|
+
function with a more secure one.
|
12
|
+
An alternative to using a trigger is to use an after_ or before_save hook in Rails. The trigger
|
13
|
+
approach is chosen as, unlike a traditional Rails app, some direct data manipulation can be expected
|
14
|
+
in Renalware, even if that is just during migration.
|
15
|
+
*/
|
16
|
+
BEGIN
|
17
|
+
IF (TG_OP = 'INSERT') THEN
|
18
|
+
NEW.external_id = renalware.pseudo_encrypt(NEW.id::integer);
|
19
|
+
RETURN NEW;
|
20
|
+
END IF;
|
21
|
+
RETURN NULL;
|
22
|
+
END $body$ LANGUAGE plpgsql VOLATILE COST 100;
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class AddExternalIdToResearchStudyParticipants < ActiveRecord::Migration[5.1]
|
2
|
+
def change
|
3
|
+
# The external_id is for obfuscating the participant id using a random integer.
|
4
|
+
# It is used e.g. when passing to an external research application
|
5
|
+
add_column(
|
6
|
+
:research_study_participants,
|
7
|
+
:external_id,
|
8
|
+
:integer,
|
9
|
+
null: true
|
10
|
+
)
|
11
|
+
|
12
|
+
reversible do |direction|
|
13
|
+
direction.up do
|
14
|
+
# Create a function the trigger will call..
|
15
|
+
load_function("update_research_study_participants_from_trigger_v01.sql")
|
16
|
+
|
17
|
+
# .. and the trigger when a row is inserted
|
18
|
+
load_trigger("update_research_study_participants_trigger_v01.sql")
|
19
|
+
|
20
|
+
# Populate the just-created external_id column with the correct value that the trigger
|
21
|
+
# would otherwise create if it were added in the future.
|
22
|
+
connection.execute(
|
23
|
+
"UPDATE renalware.research_study_participants SET external_id = renalware.pseudo_encrypt(id::integer) where external_id is NULL;"
|
24
|
+
)
|
25
|
+
end
|
26
|
+
direction.down do
|
27
|
+
connection.execute("
|
28
|
+
DROP TRIGGER IF EXISTS update_research_study_participants_trigger ON renalware.research_study_participants;
|
29
|
+
DROP FUNCTION IF EXISTS update_research_study_participants_from_trigger();
|
30
|
+
")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Now we have created and populated the external_id we need to add a unique index.
|
35
|
+
# external_id is guaranteed to be unique because the generating function (called by the
|
36
|
+
# trigger when a row is inserted) is based on the id.
|
37
|
+
# See db/functions/update_research_study_participants_from_trigger_v01
|
38
|
+
add_index :research_study_participants, :external_id, unique: true
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,6 @@
|
|
1
|
+
-- Create the trigger which will call out function every time a row in pathology_observations
|
2
|
+
-- is inserted or updated
|
3
|
+
DROP TRIGGER IF EXISTS update_research_study_participants_trigger ON renalware.research_study_participants;
|
4
|
+
CREATE TRIGGER update_research_study_participants_trigger
|
5
|
+
BEFORE INSERT ON renalware.research_study_participants FOR EACH ROW
|
6
|
+
EXECUTE PROCEDURE renalware.update_research_study_participants_from_trigger();
|
@@ -23,18 +23,35 @@ module CoreExtensions
|
|
23
23
|
load_sql_file(DatabaseObjectPaths.triggers, filename)
|
24
24
|
end
|
25
25
|
|
26
|
-
def load_sql_file(
|
27
|
-
|
26
|
+
def load_sql_file(paths, filename)
|
27
|
+
found = false
|
28
|
+
paths.each do |path|
|
29
|
+
file_path = path.join(filename)
|
30
|
+
if File.exist?(file_path)
|
31
|
+
connection.execute(File.read(file_path))
|
32
|
+
found = true
|
33
|
+
end
|
34
|
+
end
|
35
|
+
unless found
|
36
|
+
raise "Cannot file #{filename} in #{paths.join(', ')}"
|
37
|
+
end
|
28
38
|
end
|
29
39
|
|
40
|
+
# Make sure to look in the host Rails app as well as in the engine
|
30
41
|
class DatabaseObjectPaths
|
31
42
|
class << self
|
32
43
|
def triggers
|
33
|
-
|
44
|
+
[
|
45
|
+
Rails.root.join("db", "triggers"),
|
46
|
+
Renalware::Engine.root.join("db", "triggers")
|
47
|
+
]
|
34
48
|
end
|
35
49
|
|
36
50
|
def functions
|
37
|
-
|
51
|
+
[
|
52
|
+
Rails.root.join("db", "functions"),
|
53
|
+
Renalware::Engine.root.join("db", "functions")
|
54
|
+
]
|
38
55
|
end
|
39
56
|
end
|
40
57
|
end
|
data/lib/renalware/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: renalware-core
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.
|
4
|
+
version: 2.0.30
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Airslie
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-06-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: active_type
|
@@ -2878,12 +2878,14 @@ files:
|
|
2878
2878
|
- db/functions/import_practices_csv_v01.sql
|
2879
2879
|
- db/functions/preprocess_hl7_message_v01.sql
|
2880
2880
|
- db/functions/preprocess_hl7_message_v02.sql
|
2881
|
+
- db/functions/pseudo_encrypt_v01.sql
|
2881
2882
|
- db/functions/refresh_all_matierialized_views_v01.sql
|
2882
2883
|
- db/functions/refresh_current_observation_set_v01.sql
|
2883
2884
|
- db/functions/update_current_observation_set_from_trigger_v01.sql
|
2884
2885
|
- db/functions/update_current_observation_set_from_trigger_v02.sql
|
2885
2886
|
- db/functions/update_current_observation_set_from_trigger_v03.sql
|
2886
2887
|
- db/functions/update_current_observation_set_from_trigger_v04.sql
|
2888
|
+
- db/functions/update_research_study_participants_from_trigger_v01.sql
|
2887
2889
|
- db/migrate/20141004150240_create_ethnicities.rb
|
2888
2890
|
- db/migrate/20141010170329_create_death_causes.rb
|
2889
2891
|
- db/migrate/20141020170329_create_patients.rb
|
@@ -3262,6 +3264,9 @@ files:
|
|
3262
3264
|
- db/migrate/20180511171835_create_unique_indexes_on_obr_obr_codes.rb
|
3263
3265
|
- db/migrate/20180514151627_create_system_messages.rb
|
3264
3266
|
- db/migrate/20180516111411_create_view_patient_current_modalities.rb
|
3267
|
+
- db/migrate/20180605114332_create_pseudo_encrypt_function.rb
|
3268
|
+
- db/migrate/20180605141806_add_external_id_to_research_study_participants.rb
|
3269
|
+
- db/migrate/20180605175211_add_application_url_to_research_studies.rb
|
3265
3270
|
- db/seeds.rb
|
3266
3271
|
- db/seeds/default/accesses/access_pd_catheter_insertion_techniques.csv
|
3267
3272
|
- db/seeds/default/accesses/access_pd_catheter_insertion_techniques.rb
|
@@ -3346,6 +3351,7 @@ files:
|
|
3346
3351
|
- db/static.obsolete/medication_routes.yml
|
3347
3352
|
- db/triggers/feed_messages_preprocessing_trigger_v01.sql
|
3348
3353
|
- db/triggers/update_current_observation_set_trigger_v01.sql
|
3354
|
+
- db/triggers/update_research_study_participants_trigger_v01.sql
|
3349
3355
|
- db/views/medication_current_prescriptions_v01.sql
|
3350
3356
|
- db/views/pathology_current_key_observation_sets_v01.sql
|
3351
3357
|
- db/views/pathology_current_key_observation_sets_v02.sql
|