renalware-core 2.0.73 → 2.0.74

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: fa22b53287a50bfb41fc8de7c46fe936fe87387b2b209b46e34caee3e330ef5a
4
- data.tar.gz: dfd75e90ef80382dccc2118e1c7983d1ac04f0dfcb94dbca78c89d12b85ad2ba
3
+ metadata.gz: ccdebf5655509330b7b4deb66b132a39080ed140451aefb122a556e0fb04fc82
4
+ data.tar.gz: 3903cfc62c753c5019d459a0d9363a57432ed9ba20b7b1e489b5203fafc51a2e
5
5
  SHA512:
6
- metadata.gz: 71693a8d39c32435d5b2175341c9377bc472bcec42249e1fe8eb34f76f7144c02258af99c47c3cab2e47885e9af5e23777a8385508c7008b4d3b97d05d93b656
7
- data.tar.gz: a5056e47da3cd879f1e6ec4836d7846448f76aee72c38881d9500876e06252bd2f561dec5efc84d60232f5ae079a7664bea493874f49513de3f2525ca24bcdc1
6
+ metadata.gz: df449f7e2014ec25ea0e12511c96d39177454bdbc9badd36dfef1beec7f57c8b8eb3b4f21baf95953088ca672db0e922bad0ee4a096ddaf410076313839276b7
7
+ data.tar.gz: c3c2066f6fa67117a6502073de4cc61f707631f7527c56308327034a3689242251fce6befed72dd713c4117f7c269aa126f760ea11526064e981e2b1aa350164
@@ -142,6 +142,13 @@ $large-range: (64.063em, 90em);
142
142
  $xlarge-range: (90.063em, 120em);
143
143
  $xxlarge-range: (120.063em, 99999999em);
144
144
 
145
+ // Foundation defaults
146
+ // $small-range: (0, 45.78570em);
147
+ // $medium-range: (45.78571em, 73.21429em);
148
+ // $large-range: (73.21423em, 102.92857em);
149
+ // $xlarge-range: (102.9286em, 137.21429em)
150
+ //
151
+
145
152
  $screen: "only screen";
146
153
 
147
154
  $landscape: "#{$screen} and (orientation: landscape)";
@@ -66,4 +66,61 @@
66
66
  margin: auto;
67
67
  }
68
68
  }
69
+
70
+ .wrapper.wrapper_style_horizontal {
71
+ @include grid-row($behavior: nest);
72
+
73
+ > .wrapper__label {
74
+ @media #{$small-only} {
75
+ @include grid-column(12);
76
+ }
77
+ @media #{$medium-only} {
78
+ @include grid-column(4);
79
+ }
80
+ @media #{$large-up} {
81
+ @include grid-column(3);
82
+ }
83
+
84
+ label {
85
+ margin: 0 0 0.85714rem 0;
86
+ padding: 0.5rem 0;
87
+ float: none !important;
88
+ text-align: right;
89
+ display: inline;
90
+ }
91
+ }
92
+
93
+ // Medium is the default style if no other wrapper name given
94
+ > .wrapper__input {
95
+ @media #{$small-only} {
96
+ @include grid-column(12);
97
+ }
98
+ @media #{$medium-up} {
99
+ @include grid-column(6, $last-column: false);
100
+ }
101
+
102
+ .datepicker-wrapper {
103
+ max-width: 12rem !important;
104
+ }
105
+ }
106
+
107
+ &.wrapper_size_large {
108
+ > .wrapper__input {
109
+ @media #{$medium-only} {
110
+ @include grid-column(8, $last-column: false);
111
+ }
112
+ @media #{$large-up} {
113
+ @include grid-column(9, $last-column: false);
114
+ }
115
+ }
116
+ }
117
+
118
+ &.wrapper_size_small {
119
+ > .wrapper__input {
120
+ @media #{$medium-up} {
121
+ @include grid-column(4, $last-column: false);
122
+ }
123
+ }
124
+ }
125
+ }
69
126
  }
@@ -0,0 +1,22 @@
1
+ MSH|^~\&|HM|LBE|SCM||20191112164645||ORU^R01|1258271|P|2.3.1|||AL||||
2
+ PID|||Z100002^^^PAS Number||RABBIT^JESSICA^^^MS||19880924|F|||18 RABBITHOLE ROAD^LONDON^^^SE8 8JR|||||||||||||||||||
3
+ PV1||Inpatient|NIBC^^^^^^^^|||||MID^KINGS MIDWIVES||||||||||NHS|HXF888888^^^Visit Number|||||||||
4
+ ORC|RE|^PCS|09B0099478^LA||CM||||201911111841|||MID^KINGS MIDWIVES|||||||
5
+ OBR|1|^PCS|09B0099478^LA|FBC^FULL BLOOD COUNT^MB||201911111841|201911111841|||||||201911111841|B^Blood|MID^KINGS MIDWIVES||09B0099478||||201911121646||HM|F||||||||||||||||||
6
+ OBX|1|TX|WBC^WBC^MB||9.99||||||F|||201911112026||BBKA^Donald DUCK|
7
+ OBX|2|TX|RBC^RBC^MB||9.99||||||F|||201911112026||BBKA^Donald DUCK|
8
+ OBX|3|TX|HGB^Hb^MB||9.99||||||F|||201911112026||BBKA^Donald DUCK|
9
+ OBX|4|TX|PCV^PCV^MB||0.344||||||F|||201911112026||BBKA^Donald DUCK|
10
+ OBX|5|TX|MCV^MCV^MB||85.9||||||F|||201911112026||BBKA^Donald DUCK|
11
+ OBX|6|TX|MCH^MCH^MB||29.5||||||F|||201911112026||BBKA^Donald DUCK|
12
+ OBX|7|TX|MCHC^MCHC^MB||34.4||||||F|||201911112026||BBKA^Donald DUCK|
13
+ OBX|8|TX|RDW^RDW^MB||13.3||||||F|||201911112026||BBKA^Donald DUCK|
14
+ OBX|9|TX|PLT^PLT^MB||259||||||F|||201911112026||BBKA^Donald DUCK|
15
+ OBX|10|TX|MPV^Mean Platelet Volume^MB||8.3||||||F|||201911112026||BBKA^Donald DUCK|
16
+ OBX|11|TX|NRBC^Machine NRBC^MB||<0.2%||||||F|||201911112026||BBKA^Donald DUCK|
17
+ OBX|12|TX|HYPO^% HYPO^MB||0.2||||||F|||201911112026||BBKA^Donald DUCK|
18
+ OBX|13|TX|NEUT^Neutrophil Count^MB|| 3.16||||||F|||201911121646||BHISVC01^BHI Authchecker|
19
+ OBX|14|TX|LYM^Lymphocyte Count^MB|| 2.32||||||F|||201911121646||BHISVC01^BHI Authchecker|
20
+ OBX|15|TX|MON^Monocyte Count^MB|| 0.44||||||F|||201911121646||BHISVC01^BHI Authchecker|
21
+ OBX|16|TX|EOS^Eosinophil Count^MB|| 0.15||||||F|||201911121646||BHISVC01^BHI Authchecker|
22
+ OBX|17|TX|BASO^Basophils^MB|| 0.02||||||F|||201911121646||BHISVC01^BHI Authchecker|
@@ -27,13 +27,17 @@ module Renalware
27
27
  universal_service_id.split("^").first
28
28
  end
29
29
 
30
+ def name
31
+ universal_service_id.split("^")[1]
32
+ end
33
+
30
34
  # Select only OBX children. OBR can have other types of child
31
35
  # segments but we want to ignore those.
32
36
  def observations
33
37
  @observations ||= begin
34
38
  children
35
- .select{ |segment| segment.is_a? HL7::Message::Segment::OBX }
36
- .map{ |obx_segment| Observation.new(obx_segment) }
39
+ .select { |segment| segment.is_a? HL7::Message::Segment::OBX }
40
+ .map { |obx_segment| Observation.new(obx_segment) }
37
41
  end
38
42
  end
39
43
 
@@ -61,6 +65,10 @@ module Renalware
61
65
  observation_id.split("^").first
62
66
  end
63
67
 
68
+ def name
69
+ observation_id.split("^")[1]
70
+ end
71
+
64
72
  # TODO: Implement comment extraction
65
73
  def comment
66
74
  @comment || ""
@@ -102,7 +110,7 @@ module Renalware
102
110
  # There is a problem here is there are < 1 OBR
103
111
  # i.e. self[:OBR] could be an array
104
112
  def observation_requests
105
- Array(self[:OBR]).map{ |obr| ObservationRequest.new(obr) }
113
+ Array(self[:OBR]).map { |obr| ObservationRequest.new(obr) }
106
114
  end
107
115
 
108
116
  class PatientIdentification < SimpleDelegator
@@ -56,7 +56,10 @@ module Renalware
56
56
  # rubocop:disable Metrics/MethodLength
57
57
  def build_observation_request_params
58
58
  requests.each_with_object([]) do |request, arr|
59
- request_description = find_request_description(request.identifier)
59
+ request_description = find_request_description(
60
+ code: request.identifier,
61
+ name: request.name
62
+ )
60
63
  hash = {
61
64
  observation_request: {
62
65
  description_id: request_description.id,
@@ -71,9 +74,13 @@ module Renalware
71
74
  end
72
75
  # rubocop:enable Metrics/MethodLength
73
76
 
77
+ # rubocop:disable Metrics/MethodLength
74
78
  def build_observations_params(request)
75
79
  request.observations.map do |observation|
76
- observation_description = find_observation_description(observation.identifier)
80
+ observation_description = find_observation_description(
81
+ code: observation.identifier,
82
+ name: observation.name
83
+ )
77
84
  next unless validate_observation(observation, observation_description)
78
85
 
79
86
  {
@@ -85,18 +92,19 @@ module Renalware
85
92
  }
86
93
  end.compact
87
94
  end
95
+ # rubocop:enable Metrics/MethodLength
88
96
 
89
- def find_request_description(code)
97
+ def find_request_description(code:, name:)
90
98
  RequestDescription.find_or_create_by!(code: code) do |desc|
91
- desc.name = code
99
+ desc.name = name || code
92
100
  desc.lab = Lab.unknown
93
101
  end
94
102
  rescue ActiveRecord::RecordNotFound
95
103
  raise MissingRequestDescriptionError, code
96
104
  end
97
105
 
98
- def find_observation_description(code)
99
- ObservationDescription.find_or_create_by!(code: code) { |desc| desc.name = code }
106
+ def find_observation_description(code:, name:)
107
+ ObservationDescription.find_or_create_by!(code: code) { |desc| desc.name = name || code }
100
108
  rescue ActiveRecord::RecordNotFound
101
109
  raise MissingObservationDescriptionError, code
102
110
  end
@@ -50,6 +50,9 @@ module Renalware
50
50
 
51
51
  private
52
52
 
53
+ # Set the most recent status terminated_on to nil (so it is active)
54
+ # and then walk back across all other statuses and set the terminated_on
55
+ # to be started_on of the next status. Allow for > 1 status created on the same day.
53
56
  def recompute_termination_dates!
54
57
  previous_started_on = nil
55
58
  statuses.reversed.each do |status|
@@ -10,8 +10,8 @@ module Renalware
10
10
  belongs_to :description, class_name: "RegistrationStatusDescription"
11
11
  belongs_to :registration, touch: true
12
12
 
13
- scope :ordered, -> { order(started_on: :asc) }
14
- scope :reversed, -> { order(started_on: :desc) }
13
+ scope :ordered, -> { order(started_on: :asc, created_at: :asc) }
14
+ scope :reversed, -> { order(started_on: :desc, created_at: :desc) }
15
15
 
16
16
  validates :description_id, presence: true
17
17
  validates :started_on, timeliness: { type: :date, allow_blank: false }
@@ -97,22 +97,42 @@ module Renalware
97
97
  .includes(:updated_by)
98
98
  end
99
99
 
100
+ # We always send the patients current prescriptions.
100
101
  def prescriptions
101
- __getobj__.prescriptions
102
- .includes(:termination, :medication_route, :drug)
103
- .where("updated_at > ?", changes_since)
102
+ __getobj__.prescriptions.current.includes(:termination, :medication_route, :drug)
103
+ end
104
+
105
+ # We want to avoid returning duplicate pathology_observation_requests. We might have had
106
+ # and update to a pathology_observation_requests, adding a previously missing result,
107
+ # and the requestor_order_number is the same, so we have two rows with the same
108
+ # patient_id and requestor_order_number. We want to just select that latest one. We'll use
109
+ # created_at for this, even though it would be more accurate to look at the timestamp in the
110
+ # OBR or MSH segment (these not currently available).
111
+ # We use fully qualified column names here to prevent SQL errors when AR compiles the SQL.
112
+ def latest_observation_requests
113
+ Pathology::ObservationRequest
114
+ .select(<<-SELECT)
115
+ DISTINCT ON (
116
+ pathology_observation_requests.patient_id,
117
+ pathology_observation_requests.requestor_order_number)
118
+ *
119
+ SELECT
120
+ .order(<<-ORDER)
121
+ pathology_observation_requests.patient_id ASC,
122
+ pathology_observation_requests.requestor_order_number ASC,
123
+ pathology_observation_requests.created_at DESC
124
+ ORDER
104
125
  end
105
126
 
106
127
  def observation_requests
107
- pathology_patient
108
- .observation_requests
128
+ latest_observation_requests
129
+ .where(patient_id: id)
130
+ .where("requested_at >= ?", changes_since)
131
+ .where("loinc_code is not null")
109
132
  .eager_load(
110
133
  :description,
111
134
  observations: { description: :measurement_unit }
112
135
  )
113
- .where(patient_id: id)
114
- .where("requested_at >= ?", changes_since)
115
- .where("loinc_code is not null")
116
136
  end
117
137
 
118
138
  private
@@ -41,4 +41,3 @@ article.status-history.secondary
41
41
  dl.dl-horizontal
42
42
  dt Notes
43
43
  dd= simple_format status.notes
44
-
@@ -0,0 +1,57 @@
1
+ SimpleForm.setup do |config|
2
+ def configure_label(b)
3
+ b.use :html5
4
+ b.use :placeholder
5
+ b.optional :maxlength
6
+ b.optional :pattern
7
+ b.optional :min_max
8
+ b.optional :readonly
9
+
10
+ b.wrapper :label_wrapper, tag: :div, class: "wrapper__label" do |ba|
11
+ ba.use :label
12
+ end
13
+ end
14
+
15
+ def configure_input(b)
16
+ b.wrapper :right_input_wrapper, tag: :div, class: "wrapper__input" do |ba|
17
+ ba.use :input
18
+ ba.use :error, wrap_with: { tag: :small, class: ["error"] }
19
+ ba.use :hint, wrap_with: { tag: :span, class: ["hint"] }
20
+ end
21
+ end
22
+
23
+ # Configure various sized wrappers. We use CSS to style the label and inputs within the top div
24
+ # with classes of e.g. "row wrapper-medium"
25
+ %i(tiny small medium large).each do |size|
26
+ config.wrappers(
27
+ :"hz_#{size}", # eg hz_medium
28
+ tag: "div",
29
+ class: "row wrapper wrapper_style_horizontal wrapper_size_#{size}",
30
+ hint_class: :field_with_hint,
31
+ error_class: :error
32
+ ) do |b|
33
+ configure_label(b)
34
+ configure_input(b)
35
+ end
36
+ end
37
+
38
+ config.wrappers(
39
+ :hz_datepicker,
40
+ tag: :div,
41
+ class: "row wrapper wrapper_style_horizontal wrapper_size_datepicker",
42
+ hint_class: :field_with_hint,
43
+ error_class: :error
44
+ ) do |b|
45
+
46
+ configure_label(b)
47
+
48
+ b.wrapper :right_input_wrapper, tag: :div, class: "wrapper__input" do |ba|
49
+ ba.wrapper :x, tag: :div, class: "row collapse datepicker-wrapper" do |bc|
50
+ bc.use :prefix_column
51
+ bc.use :input_column
52
+ bc.use :error, wrap_with: { tag: :small, class: [:error, :datepicker_error] }
53
+ bc.use :hint, wrap_with: { tag: :span, class: :hint }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,29 @@
1
+ CREATE OR REPLACE FUNCTION renalware.new_hl7_message(message text) RETURNS void AS $$
2
+ BEGIN
3
+ /*
4
+ This fn is called by the Mirth integration engine to add an HL7 message to Renalware.
5
+ Mirth used to insert data directly into the delayed_jobs table but we are moving away from
6
+ that approach as it tightly couples Mirth to our internal implementation and prevents us
7
+ from easily moving to another background processing library eg que.
8
+
9
+ When using delayed_jobs
10
+ -----------------------
11
+ 1. We craft a yml string and translate line endings.
12
+ 2. The trigger function preprocess_hl7_message fires when a row is added to delayed_jobs.
13
+ It handles escaping odd characters eg 10^12 in the message. See that function for details.
14
+ Once we have migrated Mirth to use this function and are happy it is working we can
15
+ move that logic from preprocess_hl7_message into here and drop that function and its trigger.
16
+
17
+ When using que
18
+ ------------------
19
+ # TODO: psuedo SQL
20
+ */
21
+ insert into renalware.delayed_jobs(handler, run_at, created_at, updated_at)
22
+ values(
23
+ E'--- !ruby/struct:FeedJob\nraw_message: |\n ' || REPLACE(message, E'\r', E'\n '),
24
+ NOW() AT TIME ZONE 'UTC',
25
+ NOW() AT TIME ZONE 'UTC',
26
+ NOW() AT TIME ZONE 'UTC'
27
+ );
28
+ END;
29
+ $$ LANGUAGE plpgsql;
@@ -0,0 +1,13 @@
1
+ class CreateFunctionForHL7InsertionFromMirth < ActiveRecord::Migration[5.2]
2
+ def up
3
+ within_renalware_schema do
4
+ load_function "new_hl7_message_v01.sql"
5
+ end
6
+ end
7
+
8
+ def down
9
+ within_renalware_schema do
10
+ connection.execute("DROP FUNCTION new_hl7_message(text);")
11
+ end
12
+ end
13
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Renalware
4
- VERSION = "2.0.73"
4
+ VERSION = "2.0.74"
5
5
  end
@@ -25,5 +25,26 @@ namespace :pathology do
25
25
  hash = YAML.safe_load(*raw_message).symbolize_keys
26
26
  FeedJob.new(hash[:raw_message]).perform
27
27
  end
28
+
29
+ desc "In development only, enqueue a test HL7 message"
30
+ task enqueue_one: :environment do
31
+ raise NotImplementedError unless Rails.env.development?
32
+
33
+ # Load the example HL7 file.
34
+ path = Renalware::Engine.root.join("app", "jobs", "hl7_message_example.txt")
35
+ raw_message = File.read(path)
36
+
37
+ # Make sure line endings are \r and not \n or as that is how the HL7 looks
38
+ raw_message = raw_message.gsub /\n/, "\r"
39
+
40
+ # Replace the MSH date with now() to guarantee a unique message. not doing so results in
41
+ # an index violation becuase we calc am MD5 hash of the message and this has to be unique -
42
+ # this prevents us importing the same message twice.
43
+ raw_message = raw_message.gsub("20091112164645", Time.zone.now.strftime("%Y%m%d%H%M%S"))
44
+
45
+ ActiveRecord::Base.connection.execute(
46
+ "select renalware.new_hl7_message('#{raw_message}'::text);"
47
+ )
48
+ end
28
49
  end
29
50
  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.73
4
+ version: 2.0.74
5
5
  platform: ruby
6
6
  authors:
7
7
  - Airslie
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-03-28 00:00:00.000000000 Z
11
+ date: 2019-04-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: active_type
@@ -1343,6 +1343,7 @@ files:
1343
1343
  - app/inputs/user_picker_input.rb
1344
1344
  - app/jobs/application_job.rb
1345
1345
  - app/jobs/feed_job.rb
1346
+ - app/jobs/hl7_message_example.txt
1346
1347
  - app/jobs/hl7_message_example.yml
1347
1348
  - app/jobs/refresh_materialized_view_job.rb
1348
1349
  - app/jobs/renalware/events/save_pdf_event_to_file_job.rb
@@ -2884,6 +2885,7 @@ files:
2884
2885
  - config/initializers/simple_form_clock_picker.rb
2885
2886
  - config/initializers/simple_form_datepicker.rb
2886
2887
  - config/initializers/simple_form_foundation.rb
2888
+ - config/initializers/simple_form_wrappers.rb
2887
2889
  - config/initializers/validates_timeliness.rb
2888
2890
  - config/initializers/wicked_pdf.rb
2889
2891
  - config/initializers/wrap_parameters.rb
@@ -3028,6 +3030,7 @@ files:
3028
3030
  - db/functions/import_practice_memberships_csv_v01.sql
3029
3031
  - db/functions/import_practice_memberships_csv_v02.sql
3030
3032
  - db/functions/import_practices_csv_v01.sql
3033
+ - db/functions/new_hl7_message_v01.sql
3031
3034
  - db/functions/preprocess_hl7_message_v01.sql
3032
3035
  - db/functions/preprocess_hl7_message_v02.sql
3033
3036
  - db/functions/pseudo_encrypt_v01.sql
@@ -3456,6 +3459,7 @@ files:
3456
3459
  - db/migrate/20190218142207_add_filename_prefix_to_event_types.rb
3457
3460
  - db/migrate/20190225103005_add_info_to_hospital_centres.rb
3458
3461
  - db/migrate/20190315125638_add_timestamps_to_pathology_description_tables.rb
3462
+ - db/migrate/20190401105149_create_function_for_hl7_insertion_from_mirth.rb
3459
3463
  - db/seeds.rb
3460
3464
  - db/seeds/default/accesses/access_pd_catheter_insertion_techniques.csv
3461
3465
  - db/seeds/default/accesses/access_pd_catheter_insertion_techniques.rb