renalware-core 2.0.0.pre.rc6 → 2.0.0.pre.rc7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +0 -1
  3. data/app/controllers/renalware/events/investigations_controller.rb +10 -0
  4. data/app/controllers/renalware/transplants/donor_dashboards_controller.rb +1 -7
  5. data/app/controllers/renalware/transplants/recipient_dashboards_controller.rb +1 -21
  6. data/app/models/renalware/events/investigation.rb +31 -0
  7. data/app/models/renalware/feeds/hl7_message.rb +29 -34
  8. data/app/models/renalware/feeds/message_parser.rb +2 -1
  9. data/app/models/renalware/pathology/create_observation_requests.rb +25 -0
  10. data/app/models/renalware/pathology/message_listener.rb +7 -5
  11. data/app/models/renalware/pathology/{message_param_parser.rb → observation_requests_attributes_builder.rb} +31 -27
  12. data/app/models/renalware/patients/practice_search_query.rb +1 -1
  13. data/app/policies/renalware/events/investigation_policy.rb +8 -0
  14. data/app/presenters/renalware/pathology/observation_presenter.rb +1 -1
  15. data/app/presenters/renalware/transplants/consent_presenter.rb +1 -1
  16. data/app/presenters/renalware/transplants/donor_dashboard_presenter.rb +35 -0
  17. data/app/presenters/renalware/transplants/mdm_presenter.rb +2 -0
  18. data/app/presenters/renalware/transplants/patient_presenter.rb +2 -0
  19. data/app/presenters/renalware/transplants/recipient_dashboard_presenter.rb +39 -0
  20. data/app/presenters/renalware/transplants/wait_list_registration_presenter.rb +2 -0
  21. data/app/views/renalware/api/ukrdc/patients/_documents.xml.builder +12 -9
  22. data/app/views/renalware/api/ukrdc/patients/_sending_facility.xml.builder +4 -4
  23. data/app/views/renalware/api/ukrdc/patients/lab_orders/_lab_order.xml.builder +1 -1
  24. data/app/views/renalware/api/ukrdc/patients/lab_orders/_result_item.xml.builder +1 -1
  25. data/app/views/renalware/api/ukrdc/patients/show.xml.builder +1 -5
  26. data/app/views/renalware/events/events/_event.html.slim +7 -1
  27. data/app/views/renalware/events/events/_table.html.slim +2 -1
  28. data/app/views/renalware/events/events/cell/_investigation.html.slim +9 -0
  29. data/app/views/renalware/events/events/inputs/_investigation.html.slim +4 -0
  30. data/app/views/renalware/events/events/toggled_cell/_investigation.html.slim +11 -0
  31. data/app/views/renalware/events/investigations/_list.html.slim +12 -0
  32. data/app/views/renalware/events/investigations/edit.html.slim +13 -0
  33. data/app/views/renalware/transplants/donor_dashboards/show.html.slim +27 -20
  34. data/app/views/renalware/transplants/recipient_dashboards/_page_actions.html.slim +4 -0
  35. data/app/views/renalware/transplants/recipient_dashboards/show.html.slim +22 -17
  36. data/config/locales/renalware/events/investigation.en.yml +58 -0
  37. data/config/locales/renalware/transplants/donor_dashboard.en.yml +1 -0
  38. data/config/locales/renalware/transplants/recipient_dashboards.en.yml +1 -0
  39. data/config/routes.rb +5 -0
  40. data/db/migrate/20180119121243_create_trigger_to_preprocess_hl7_msg.rb +5 -3
  41. data/db/migrate/20180125201356_make_obs_set_trigger_change_updated_at.rb +191 -0
  42. data/db/migrate/20180126142314_add_uuid_to_letters.rb +6 -0
  43. data/db/seeds/default/events/event_types.csv +1 -0
  44. data/lib/renalware/configuration.rb +1 -0
  45. data/lib/renalware/version.rb +1 -1
  46. data/lib/test_support/text_editor_helpers.rb +6 -0
  47. data/spec/factories/events/events.rb +24 -1
  48. data/spec/factories/events/events_types.rb +17 -2
  49. data/spec/support/database_functions_spec_helper.rb +6 -0
  50. metadata +18 -4
  51. data/app/models/renalware/pathology/create_observations.rb +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ce584cb7cbfefee7174f99e866b6a7955c12266f2fd6b0a60d86078dbf342ee2
4
- data.tar.gz: 63261ae22a2fa334bb74777b0312ca3101a0cc38b45f964fd15ffaa82d62e7f6
3
+ metadata.gz: fe0192f8e977702f2adbf2a0f2893b3552b9e6bd573f721208e0250e03e03312
4
+ data.tar.gz: 87a77ba05e6a2dfb83cddbdb4d1d2de1bfe19aaf2c50515e57677db00588f7d5
5
5
  SHA512:
6
- metadata.gz: e0f645e776c510e2b1f65c9b786b5838af19d350cc762d01ce6648f207881f6239ae131a10c980264cd123ed5ce6eaa0b1dcd13c2d0e62b2c6576df4feb32cc7
7
- data.tar.gz: 0035ed970b1e8776a586398a8ad05e51373dff09467f833d6950f311eaf4e8508b63649849699e599b619c1fe4e689cb3334785ab91da31f1706bb677651ff02
6
+ metadata.gz: 3f6ef5e4f2f0fc65a67b0d6cb9c649685ec7ed98db4b0129a1ffb5f96d566e6a283f1fd436cff669188cd34778561b696f2a68c86b9d539790131110b6705e00
7
+ data.tar.gz: e61ff85f88683a31976a64dc74dfd65be9250168c7de67225c73db358591b387cef1f8add0ddb9bbf08cb6ade2becdbcc8e4f142de7c6893f4bde1c412dd027d
data/README.md CHANGED
@@ -207,7 +207,6 @@ To make awesome_print the default formatter in irb, add the following to `~/.irb
207
207
  require "awesome_print"
208
208
  AwesomePrint.irb!
209
209
 
210
-
211
210
  #### Docker - WIP!
212
211
 
213
212
  ```
@@ -0,0 +1,10 @@
1
+ require_dependency "renalware/events"
2
+
3
+ # This controller is mostly empty - we are using it for mainly routing and to let us
4
+ # override the events 'new' and 'edit' templates. See the base class for most functionality.
5
+ module Renalware
6
+ module Events
7
+ class InvestigationsController < EventsController
8
+ end
9
+ end
10
+ end
@@ -6,13 +6,7 @@ module Renalware
6
6
  before_action :load_patient
7
7
 
8
8
  def show
9
- render locals: {
10
- patient: patient,
11
- donations: Donation.for_patient(patient).reversed,
12
- donor_workup: DonorWorkup.for_patient(patient).first_or_initialize,
13
- donor_operations: DonorOperation.for_patient(patient).reversed,
14
- donor_stages: DonorStage.for_patient(patient).ordered
15
- }
9
+ render locals: { dashboard: DonorDashboardPresenter.new(patient) }
16
10
  end
17
11
  end
18
12
  end
@@ -5,27 +5,7 @@ module Renalware
5
5
  class RecipientDashboardsController < BaseController
6
6
  def show
7
7
  authorize patient
8
- render locals: locals
9
- end
10
-
11
- private
12
-
13
- def locals
14
- {
15
- patient: patient,
16
- recipient_workup: RecipientWorkup.for_patient(patient).first_or_initialize,
17
- registration: registration_presenter,
18
- recipient_operations: RecipientOperation.for_patient(patient).reversed,
19
- donations: Donation.for_recipient(patient).reversed
20
- }
21
- end
22
-
23
- def registration_presenter
24
- WaitListRegistrationPresenter.new(registration)
25
- end
26
-
27
- def registration
28
- Registration.for_patient(patient).first_or_initialize
8
+ render locals: { dashboard: RecipientDashboardPresenter.new(patient) }
29
9
  end
30
10
  end
31
11
  end
@@ -0,0 +1,31 @@
1
+ require_dependency "renalware/events"
2
+ require "document/base"
3
+ require "document/embedded"
4
+ require "document/enum"
5
+
6
+ module Renalware
7
+ module Events
8
+ class Investigation < Event
9
+ include Document::Base
10
+
11
+ scope :transplant_donors, lambda{
12
+ where("document ->> 'modality' = ?", "transplant_donor")
13
+ }
14
+ scope :transplant_recipients, lambda{
15
+ where("document ->> 'modality' = ?", "transplant_recipient")
16
+ }
17
+
18
+ class Document < Document::Embedded
19
+ attribute :modality,
20
+ ::Document::Enum,
21
+ enums: %i(transplant_donor transplant_recipient other)
22
+ attribute :type, ::Document::Enum # See i18n for options
23
+ attribute :result, String
24
+ validates :modality, presence: true
25
+ validates :type, presence: true
26
+ validates :result, presence: true
27
+ end
28
+ has_document
29
+ end
30
+ end
31
+ end
@@ -15,8 +15,9 @@ module Renalware
15
15
  end
16
16
 
17
17
  class ObservationRequest < SimpleDelegator
18
- def initialize(observation_request_segment, observations_segments)
19
- @observations_segments = observations_segments
18
+ alias_attribute :date_time, :observation_date
19
+
20
+ def initialize(observation_request_segment)
20
21
  super(observation_request_segment)
21
22
  end
22
23
 
@@ -24,8 +25,14 @@ module Renalware
24
25
  universal_service_id.split("^").first
25
26
  end
26
27
 
28
+ # Select only OBX children. OBR can have other types of child
29
+ # segments but we want to ignore those.
27
30
  def observations
28
- Array(@observations_segments).map { |segment| Observation.new(segment) }
31
+ @observations ||= begin
32
+ children
33
+ .select{ |segment| segment.is_a? HL7::Message::Segment::OBX }
34
+ .map{ |obx_segment| Observation.new(obx_segment) }
35
+ end
29
36
  end
30
37
 
31
38
  def ordering_provider_name
@@ -36,10 +43,6 @@ module Renalware
36
43
  super.split("^").first
37
44
  end
38
45
 
39
- def date_time
40
- observation_date
41
- end
42
-
43
46
  private
44
47
 
45
48
  def ordering_provider
@@ -48,6 +51,9 @@ module Renalware
48
51
  end
49
52
 
50
53
  class Observation < SimpleDelegator
54
+ alias_attribute :date_time, :observation_date
55
+ alias_attribute :value, :observation_value
56
+
51
57
  def identifier
52
58
  observation_id.split("^").first
53
59
  end
@@ -57,14 +63,6 @@ module Renalware
57
63
  ""
58
64
  end
59
65
 
60
- def date_time
61
- observation_date
62
- end
63
-
64
- def value
65
- observation_value
66
- end
67
-
68
66
  # Because some units of measurement, such as 10^12/L for WBC, contain a caret, the caret
69
67
  # will initially have been encoded by Mirth as \S\ (a Mirth escape sequence for ^
70
68
  # or whatever the mirth component separator character is configured to be)
@@ -72,28 +70,33 @@ module Renalware
72
70
  # the `\12` within the message is interpreted as a `\n` (form feed) by
73
71
  # delayed_job when it is read into the yaml format string in the HL7 messages.
74
72
  # While it might be possible to write out yaml into delayed_job using a format
75
- # that will not un-escape on reading, the approach here is that the Mirth channel must
76
- # encode \S\ as \\S\\ in the message before inserting.
77
- # Thus the raw data for units might look like `10\\S\\12/L` and this will work
78
- # by the fact that the backslashes are escaped and a significant `\12` is not found.
73
+ # that will not un-escape on reading, the approach here is that the we have preprocessed
74
+ # the message using a trigger (at the point it is inserted into delayed_jobs) by
75
+ # replacing any instance of \S\ with \\S\\ in the message.
76
+ # Thus the raw data for units in the database will look like `10\\S\\12/L`.
77
+ # When ever this string is loaded by Ruby it will un-escaped and become "\S\"
78
+ # No `\12` is not found and un-escaped to \n"
79
+ # Note in the gsub here we double escape the \'s
79
80
  def units
80
81
  super&.gsub("\\S\\", "^")
81
82
  end
82
83
  end
83
84
 
84
- def observation_request
85
- ObservationRequest.new(self[:OBR], self[:OBX])
85
+ # There is a problem here is there are < 1 OBR
86
+ # i.e. self[:OBR] could be an array
87
+ def observation_requests
88
+ Array(self[:OBR]).map{ |obr| ObservationRequest.new(obr) }
86
89
  end
87
90
 
88
91
  class PatientIdentification < SimpleDelegator
92
+ alias_attribute :external_id, :patient_id
93
+ alias_attribute :sex, :admin_sex
94
+ alias_attribute :dob, :patient_dob
95
+
89
96
  def internal_id
90
97
  patient_id_list.split("^").first
91
98
  end
92
99
 
93
- def external_id
94
- patient_id
95
- end
96
-
97
100
  def name
98
101
  Name.new(patient_name)
99
102
  end
@@ -106,14 +109,6 @@ module Renalware
106
109
  patient_name[1]
107
110
  end
108
111
 
109
- def sex
110
- admin_sex
111
- end
112
-
113
- def dob
114
- patient_dob
115
- end
116
-
117
112
  private
118
113
 
119
114
  def patient_name
@@ -134,7 +129,7 @@ module Renalware
134
129
  end
135
130
 
136
131
  def to_s
137
- @message_string
132
+ @message_string.tr("\r", "\n")
138
133
  end
139
134
  end
140
135
  end
@@ -7,7 +7,8 @@ module Renalware
7
7
  #
8
8
  class MessageParser
9
9
  def parse(message_string)
10
- HL7Message.new(message_string)
10
+ lines = message_string.split("\n").join("\r")
11
+ HL7Message.new(lines)
11
12
  end
12
13
  end
13
14
  end
@@ -0,0 +1,25 @@
1
+ require_dependency "renalware/pathology"
2
+
3
+ #
4
+ # Create pathology observations requests and their child observations for an existing
5
+ # patient from HL7 message content.
6
+ #
7
+ module Renalware
8
+ module Pathology
9
+ class CreateObservationRequests
10
+ def call(params)
11
+ Array(params).each do |request|
12
+ patient = find_patient(request.fetch(:patient_id))
13
+ observation_params = request.fetch(:observation_request)
14
+ patient.observation_requests.create!(observation_params)
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def find_patient(id)
21
+ ::Renalware::Pathology::Patient.find_by(id: id) || NullObject.instance
22
+ end
23
+ end
24
+ end
25
+ end
@@ -9,19 +9,21 @@ module Renalware
9
9
  class MessageListener
10
10
  def message_processed(message_payload)
11
11
  pathology_params = parse_pathology_params(message_payload)
12
- create_observations(pathology_params)
13
- # Note the the current_observation_set for the patient is updated by a trigger here
12
+ create_observation_requests_and_their_child_observations_from(pathology_params)
13
+ #
14
+ # Note: The the current_observation_set for the patient is updated by a trigger here
15
+ #
14
16
  end
15
17
 
16
18
  private
17
19
 
18
20
  def parse_pathology_params(message_payload)
19
- MessageParamParser.new(message_payload).parse
21
+ ObservationRequestsAttributesBuilder.new(message_payload).parse
20
22
  end
21
23
 
22
- def create_observations(pathology_params)
24
+ def create_observation_requests_and_their_child_observations_from(pathology_params)
23
25
  return if pathology_params.nil? # eg patient does not exist
24
- CreateObservations.new.call(pathology_params)
26
+ CreateObservationRequests.new.call(pathology_params)
25
27
  end
26
28
  end
27
29
  end
@@ -4,12 +4,14 @@ module Renalware
4
4
  module Pathology
5
5
  # Responsible for transforming an HL7 message payload into a params hash
6
6
  # that can be persisted by ObservationRequest.
7
- #
8
- class MessageParamParser
9
- delegate :patient_identification, :observation_request, to: :message_payload
7
+ # Note:
8
+ # - A message can have multiple observation_requests, each with its own observations.
9
+ # - This class could be removed and a Builder class used to create the database models
10
+ # directly - this would remove the extra level of indirection that this class introduces.
11
+ class ObservationRequestsAttributesBuilder
12
+ delegate :patient_identification, :observation_requests, to: :message_payload
10
13
  delegate :internal_id, to: :patient_identification
11
- delegate :observations, to: :observation_request
12
- alias_attribute :request, :observation_request
14
+ alias_attribute :requests, :observation_requests
13
15
 
14
16
  # message_payload is an HL7Message (a decorator around an ::HL7::Message)
15
17
  def initialize(message_payload, logger = Delayed::Worker.logger)
@@ -17,6 +19,9 @@ module Renalware
17
19
  @logger = logger
18
20
  end
19
21
 
22
+ # Return an array of observation request attributes (with a nested array of
23
+ # child observation attributes) for each OBR in the HL7 message.
24
+ # The resulting array will be used to create the corresponding database records.
20
25
  def parse
21
26
  if renalware_patient?
22
27
  build_patient_params
@@ -34,32 +39,38 @@ module Renalware
34
39
 
35
40
  attr_reader :message_payload, :logger
36
41
 
37
- def observations_params
38
- @observations_params ||= build_observations_params
42
+ def build_patient_params
43
+ patient = find_patient(internal_id)
44
+ request_params.each do |request_param|
45
+ request_param[:patient_id] = patient.id
46
+ end
39
47
  end
40
48
 
41
49
  def request_params
42
50
  @request_params ||= build_observation_request_params
43
51
  end
44
52
 
53
+ # rubocop:disable Metrics/MethodLength
45
54
  def build_observation_request_params
46
- request_description = find_request_description(request.identifier)
47
-
48
- {
49
- observation_request: {
50
- description_id: request_description.id,
51
- requestor_name: request.ordering_provider_name,
52
- requestor_order_number: request.placer_order_number,
53
- requested_at: parse_time(request.date_time),
54
- observations_attributes: observations_params
55
+ requests.each_with_object([]) do |request, arr|
56
+ request_description = find_request_description(request.identifier)
57
+ hash = {
58
+ observation_request: {
59
+ description_id: request_description.id,
60
+ requestor_name: request.ordering_provider_name,
61
+ requestor_order_number: request.placer_order_number,
62
+ requested_at: parse_time(request.date_time),
63
+ observations_attributes: build_observations_params(request)
64
+ }
55
65
  }
56
- }
66
+ arr << hash
67
+ end
57
68
  end
69
+ # rubocop:enable Metrics/MethodLength
58
70
 
59
- def build_observations_params
60
- observations.map do |observation|
71
+ def build_observations_params(request)
72
+ request.observations.map do |observation|
61
73
  observation_description = find_observation_description(observation.identifier)
62
-
63
74
  {
64
75
  description_id: observation_description.id,
65
76
  observed_at: parse_time(observation.date_time),
@@ -69,13 +80,6 @@ module Renalware
69
80
  end
70
81
  end
71
82
 
72
- def build_patient_params
73
- request_params.tap do |p|
74
- patient = find_patient(internal_id)
75
- p[:patient_id] = patient.id
76
- end
77
- end
78
-
79
83
  def find_request_description(code)
80
84
  RequestDescription.find_by!(code: code)
81
85
  end
@@ -12,7 +12,7 @@ module Renalware
12
12
 
13
13
  term = "%#{search_term}%"
14
14
  Practice.select(:id, :name)
15
- .joins(:address)
15
+ .left_outer_joins(:address)
16
16
  .where("patient_practices.name ILIKE ? OR addresses.postcode ILIKE ?", term, term)
17
17
  # .select("patient_practices.id", "patient_practices.name")
18
18
  end
@@ -0,0 +1,8 @@
1
+ require_dependency "renalware/events"
2
+
3
+ module Renalware
4
+ module Events
5
+ class InvestigationPolicy < BasePolicy
6
+ end
7
+ end
8
+ end
@@ -3,7 +3,7 @@ require_dependency "renalware/pathology"
3
3
  module Renalware
4
4
  module Pathology
5
5
  class ObservationPresenter < SimpleDelegator
6
- delegate :name, :code, to: :description, prefix: true, allow_nil: true
6
+ delegate :name, :code, :loinc_code, to: :description, prefix: true, allow_nil: true
7
7
  delegate :measurement_unit, to: :description
8
8
  delegate :name, to: :measurement_unit, prefix: true
9
9
 
@@ -1,4 +1,4 @@
1
- require_dependency "renalware"
1
+ require_dependency "renalware/transplants"
2
2
 
3
3
  module Renalware
4
4
  module Transplants
@@ -0,0 +1,35 @@
1
+ require_dependency "renalware/transplants"
2
+ require "attr_extras"
3
+
4
+ module Renalware
5
+ module Transplants
6
+ class DonorDashboardPresenter
7
+ attr_reader_initialize :patient
8
+
9
+ def donor_stages
10
+ @donor_stages ||= DonorStage.for_patient(patient).ordered
11
+ end
12
+
13
+ def donor_workup
14
+ @donor_workups ||= DonorWorkup.for_patient(patient).first_or_initialize
15
+ end
16
+
17
+ def donations
18
+ @donations ||= Donation.for_patient(patient).reversed
19
+ end
20
+
21
+ def donor_operations
22
+ @donor_operations ||= DonorOperation.for_patient(patient).reversed
23
+ end
24
+
25
+ def investigations
26
+ @investigations ||= begin
27
+ Events::Investigation
28
+ .for_patient(patient)
29
+ .transplant_donors
30
+ .ordered
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end