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

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.
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