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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7eb14ab1adcb2a713e62aca7a0f77d4abe7714a5d22c5af5623dfd9cd528656
4
- data.tar.gz: b58bef85d36bdb2af561a3229a51aac0bb690b3498cab8804076fdc4a5a5d149
3
+ metadata.gz: b29e1fc4d6a1bb7e86fdcf56f18246fd8e39119376366f7017838cedfd4b087f
4
+ data.tar.gz: d5cd02cdd2dcb4b42cd8c5d0ecc32f7dfce445e45a00adee57556b49adba4ad1
5
5
  SHA512:
6
- metadata.gz: f89c0255c15fa0edeca0f64fc3c07fa4a71b2a5103d8491cfe350ceb0adf23dd9665a10ac8ce003a2f95d5a8327c1c9325626311bb94dc8b033511d34405c75c
7
- data.tar.gz: 776a1be9ade866026a0ed0cc7956eeb7983194c6da7a57b192a74ca16b15721865fdfda5d1442826a0673b09e27caa60922550854352f5c05c239cbfd4f1cf52
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
- @media pring {
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, 5);
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(:code, :description, :leader, :notes, :started_on, :terminated_on)
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 = Renalware::Clinical::DryWeight.for_patient(patient).first
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,9 @@
1
+ class CreatePseudoEncryptFunction < ActiveRecord::Migration[5.1]
2
+ def up
3
+ load_function "pseudo_encrypt_v01.sql"
4
+ end
5
+
6
+ def down
7
+ connection.execute("DROP FUNCTION renalware.pseudo_encrypt(int);")
8
+ end
9
+ end
@@ -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,5 @@
1
+ class AddApplicationUrlToResearchStudies < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :research_studies, :application_url, :string, null: true
4
+ end
5
+ 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(path, filename)
27
- connection.execute(File.read(path.join(filename)))
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
- Renalware::Engine.root.join("db", "triggers")
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
- Renalware::Engine.root.join("db", "functions")
51
+ [
52
+ Rails.root.join("db", "functions"),
53
+ Renalware::Engine.root.join("db", "functions")
54
+ ]
38
55
  end
39
56
  end
40
57
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Renalware
4
- VERSION = "2.0.28"
4
+ VERSION = "2.0.30"
5
5
  end
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.28
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-05-31 00:00:00.000000000 Z
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