renalware-core 2.0.28 → 2.0.30
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 +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
|