renalware-core 2.0.73 → 2.0.74

Sign up to get free protection for your applications and to get access to all the features.
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