renalware-core 2.0.0.pre.rc1 → 2.0.0.pre.rc3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/renalware/modules/_dashboard.scss +5 -4
  3. data/app/controllers/renalware/admin/users_controller.rb +6 -2
  4. data/app/controllers/renalware/admissions/consults_controller.rb +1 -1
  5. data/app/controllers/renalware/letters/contacts_controller.rb +4 -1
  6. data/app/controllers/renalware/pathology/current_observation_results_controller.rb +5 -11
  7. data/app/controllers/renalware/research/study_participants_controller.rb +1 -1
  8. data/app/helpers/renalware/dashboards_helper.rb +10 -0
  9. data/app/helpers/renalware/layout_helper.rb +3 -1
  10. data/app/models/concerns/renalware/letters/letter_pathology.rb +20 -0
  11. data/app/models/concerns/renalware/patient_pathology_scopes.rb +15 -7
  12. data/app/models/renalware/admissions/consult.rb +4 -3
  13. data/app/models/renalware/admissions/consult_site.rb +9 -0
  14. data/app/models/renalware/hd/mdm_patients_query.rb +2 -1
  15. data/app/models/renalware/letters/draft_letter.rb +2 -1
  16. data/app/models/renalware/letters/letter.rb +1 -0
  17. data/app/models/renalware/letters/part/recent_pathology_results.rb +2 -16
  18. data/app/models/renalware/letters/revise_letter.rb +2 -0
  19. data/app/models/renalware/pathology/{current_key_observation_set.rb → current_key_observation_set.rb.dead} +0 -0
  20. data/app/models/renalware/pathology/current_observation_set.rb +35 -0
  21. data/app/models/renalware/pathology/message_listener.rb +5 -3
  22. data/app/models/renalware/pathology/message_param_parser.rb +35 -10
  23. data/app/models/renalware/pathology/observation.rb +1 -1
  24. data/app/models/renalware/pathology/observation_request.rb +4 -1
  25. data/app/models/renalware/pathology/observations_jsonb_serializer.rb +73 -0
  26. data/app/models/renalware/pathology/patient.rb +4 -0
  27. data/app/models/renalware/pathology/update_current_observations.rb.dead +25 -0
  28. data/app/models/renalware/patient.rb +1 -1
  29. data/app/models/renalware/patients/mdm_patients_query.rb +4 -8
  30. data/app/models/renalware/pd/mdm_patients_query.rb +4 -8
  31. data/app/models/renalware/renal/low_clearance/mdm_patients_query.rb +5 -4
  32. data/app/models/renalware/transplants/mdm_patients_query.rb +4 -1
  33. data/app/presenters/renalware/admissions/consult_presenter.rb +6 -2
  34. data/app/presenters/renalware/dashboard/dashboard_presenter.rb +0 -4
  35. data/app/presenters/renalware/hd/patient_presenter.rb +6 -0
  36. data/app/presenters/renalware/mdm_patient_presenter.rb +6 -10
  37. data/app/presenters/renalware/pathology/{current_observation_results/html_table_view.rb → current_observation_results.dead/html_table_view.rb.dead} +0 -0
  38. data/app/presenters/renalware/pathology/{current_observation_results/presenter.rb → current_observation_results.dead/presenter.rb.dead} +0 -0
  39. data/app/presenters/renalware/pathology/observation_set_presenter.rb +57 -0
  40. data/app/presenters/renalware/pathology/observations_diff.rb +97 -77
  41. data/app/presenters/renalware/pathology/patient_presenter.rb +0 -4
  42. data/app/presenters/renalware/renal/clinical_summary_presenter.rb +1 -1
  43. data/app/views/renalware/admin/users/_filters.html.slim +19 -0
  44. data/app/views/renalware/admin/users/index.html.slim +9 -12
  45. data/app/views/renalware/admissions/consults/_filters.html.slim +2 -2
  46. data/app/views/renalware/admissions/consults/_form.html.slim +13 -53
  47. data/app/views/renalware/admissions/consults/_table.html.slim +3 -3
  48. data/app/views/renalware/dashboard/dashboards/show.html.slim +1 -1
  49. data/app/views/renalware/hd/mdm_patients/_patient.html.slim +2 -2
  50. data/app/views/renalware/letters/letters/_pathology.html.slim +2 -2
  51. data/app/views/renalware/letters/parts/_recent_pathology_results.html.slim +3 -3
  52. data/app/views/renalware/mdm_patients/_patient.html.slim +7 -7
  53. data/app/views/renalware/pathology/current_observation_results/index.html.slim +12 -1
  54. data/app/views/renalware/pathology/observations/_diff.html.slim +26 -20
  55. data/config/initializers/core_extensions.rb +2 -0
  56. data/config/locales/renalware/admissions/consults.en.yml +9 -0
  57. data/config/locales/renalware/dashboard/dashboard.yml +1 -1
  58. data/config/locales/renalware/patient.yml +0 -1
  59. data/db/migrate/20171204112150_create_consult_sites.rb +14 -0
  60. data/db/migrate/20171211161400_create_pathology_current_table.rb +19 -0
  61. data/db/migrate/20171213111513_create_fn_to_refresh_current_obs.rb +50 -0
  62. data/db/migrate/20171214141335_create_trigger_to_update_current_observation_sets.rb +111 -0
  63. data/db/migrate/20171214190849_enforce_request_id_on_observations.rb +5 -0
  64. data/db/migrate/20171215122454_add_pathology_observation_set_to_letters.rb +10 -0
  65. data/lib/core_extensions/hash.rb +11 -0
  66. data/lib/renalware/version.rb +1 -1
  67. data/spec/factories/admissions/consult_sites.rb +6 -0
  68. data/spec/factories/admissions/consults.rb +1 -1
  69. metadata +21 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 368a9f7e47981dd189746ea33f2f9170da40feea
4
- data.tar.gz: 661e00b27713f1b17a461bfff57e9afda54ce746
3
+ metadata.gz: c3c267233dd81ec4b3886e524d833d785dc3a371
4
+ data.tar.gz: cdcad7175c0fe8258a43f4dced4e0d3f159683bc
5
5
  SHA512:
6
- metadata.gz: 343b0bab9e105e8413782e4c4bd310e6ae25982090e8bd1cfdde21e72b3c914b458914f6a42078a0fd5a5c5ad2064144fe3e0b8d66d49c517706e421da0e3830
7
- data.tar.gz: 5745969d2babe47614fdcb95717fb837071c0dfce4b6f782c1d60a7418792efb3a380b7409c520a4fc2f3ceeaa249412eb7c08f6a29ddce065f6f3bb0d088be4
6
+ metadata.gz: f0ec0bdf814791b21874625beaa39075fdee412a80e615bb30c61b206ebcf90f79f7bae00a6c898c83b72af690be3d4a0f65b94fca139aba357fab10af1193c1
7
+ data.tar.gz: a06f5d25faccc97b6374a910b9d3cd4ecf8c23a493bf329dc85e3ab8920cd0c203fbfecb251d60c3ac833b7dce6adb510a7fbf54db9403807c2eab14bbbc6abe
@@ -1,13 +1,14 @@
1
1
  .dashboard-content {
2
2
  margin-top: 10px;
3
3
 
4
- section {
5
- // TODO: Style sections
6
- }
7
-
8
4
  p.empty-section {
9
5
  padding-left: 0;
10
6
  color: $dashboard-muted-color;
11
7
  clear: both;
12
8
  }
13
9
  }
10
+
11
+ .dashboard-owner {
12
+ color: $mid-grey;
13
+ font-style: normal;
14
+ }
@@ -3,10 +3,14 @@ module Renalware
3
3
  include Renalware::Concerns::Pageable
4
4
 
5
5
  def index
6
- search = User.search(params[:q])
6
+ query = params.fetch(:q, {})
7
+ query[:s] ||= "family_name"
8
+ search = User
9
+ .where.not(username: :systemuser)
10
+ .search(query)
7
11
  users = search.result(distinct: true).page(page).per(per_page)
8
12
  authorize users
9
- render locals: { users: users }
13
+ render locals: { users: users, user_search: search }
10
14
  end
11
15
 
12
16
  def edit
@@ -70,7 +70,7 @@ module Renalware
70
70
  params
71
71
  .require(:admissions_consult)
72
72
  .permit(
73
- :hospital_unit_id, :hospital_ward_id, :patient_id, :q,
73
+ :consult_site_id, :hospital_ward_id, :patient_id, :q, :other_site_or_ward,
74
74
  :decided_on, :transferred_on, :started_on, :ended_on, :decided_on,
75
75
  :aki_risk, :transfer_priority, :seen_by_id, :consult_type,
76
76
  :requires_aki_nurse, :description, :contact_number
@@ -38,7 +38,10 @@ module Renalware
38
38
  end
39
39
 
40
40
  def find_contacts
41
- CollectionPresenter.new(@patient.contacts.includes(:description).ordered, ContactPresenter)
41
+ CollectionPresenter.new(
42
+ @patient.contacts.includes(:description).ordered,
43
+ ContactPresenter
44
+ )
42
45
  end
43
46
 
44
47
  def find_contact_descriptions
@@ -3,18 +3,12 @@ require_dependency "renalware/pathology"
3
3
  module Renalware
4
4
  module Pathology
5
5
  class CurrentObservationResultsController < Pathology::BaseController
6
- before_action :load_patient
7
-
8
6
  def index
9
- table_view = CurrentObservationResults::HTMLTableView.new(view_context)
10
- presenter = CurrentObservationResults::Presenter.new
11
- service = ViewCurrentObservationResults.new(@patient, presenter)
12
- service.call
13
-
14
- render :index, locals: {
15
- rows: presenter.view_model,
16
- table: table_view
17
- }
7
+ patient = load_patient
8
+ observation_set = ObservationSetPresenter.new(
9
+ patient.fetch_current_observation_set
10
+ )
11
+ render :index, locals: { observation_set: observation_set }
18
12
  end
19
13
  end
20
14
  end
@@ -52,7 +52,7 @@ module Renalware
52
52
  private
53
53
 
54
54
  def participants
55
- @participants ||= study.participants.page(page).per(per_page)
55
+ @participants ||= study.participants.includes(:patient).page(page).per(per_page)
56
56
  end
57
57
 
58
58
  def render_edit(participant)
@@ -0,0 +1,10 @@
1
+ module Renalware
2
+ module DashboardsHelper
3
+ def composed_dashboard_title(user_name)
4
+ capture do
5
+ concat content_tag(:span, I18n.t("renalware.dashboard.dashboards.title"))
6
+ concat content_tag(:span, user_name, class: "dashboard-owner")
7
+ end
8
+ end
9
+ end
10
+ end
@@ -1,8 +1,10 @@
1
1
  module Renalware
2
2
  module LayoutHelper
3
+ # If you don't want the title argument to be used in the browser bar, pass in
4
+ # page_title: in opts also.
3
5
  def within_admin_layout(title: nil, **opts)
4
6
  within_layout(layout: "renalware/layouts/non_patient",
5
- title: title,
7
+ title: opts.fetch(:page_title, title),
6
8
  **opts) { yield }
7
9
  end
8
10
 
@@ -0,0 +1,20 @@
1
+ require_dependency "renalware/letters"
2
+
3
+ module Renalware
4
+ module Letters
5
+ module LetterPathology
6
+ extend ActiveSupport::Concern
7
+
8
+ # Update a letter's pathology snapshot to the current point in time
9
+ def build_pathology_snapshot(patient, letter)
10
+ return if patient.current_observation_set.nil?
11
+ possible_letter_codes = Renalware::Letters::RelevantObservationDescription.all.map(&:code)
12
+ letter.pathology_snapshot = begin
13
+ patient.current_observation_set.values.select do |code, _|
14
+ possible_letter_codes.include?(code.to_s)
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -6,8 +6,8 @@ require_dependency "renalware"
6
6
  #
7
7
  module Renalware
8
8
  module PatientPathologyScopes
9
- def with_current_key_pathology
10
- includes(:current_key_observation_set).joins(:current_key_observation_set)
9
+ def with_current_pathology
10
+ includes(:current_observation_set)
11
11
  end
12
12
 
13
13
  # Define some ransackers to make it easier to sort the table (using sort_link)
@@ -23,17 +23,25 @@ module Renalware
23
23
  #
24
24
  def self.extended(base)
25
25
  %i(hgb ure cre).each do |code|
26
- base.ransacker(code) { pathology_sort_predicate("#{code}_result") }
27
- base.ransacker(:"#{code}_date") { pathology_sort_predicate("#{code}_observed_at") }
26
+ base.ransacker(code) { pathology_result_sort_predicate(code) }
27
+ base.ransacker(:"#{code}_date") { pathology_date_sort_predicate(code) }
28
28
  end
29
29
 
30
30
  %i(mdrd).each do |code|
31
- base.ransacker(code) { pathology_sort_predicate("#{code}_result") }
31
+ base.ransacker(code) { pathology_result_sort_predicate(code) }
32
32
  end
33
33
  end
34
34
 
35
- def self.pathology_sort_predicate(column)
36
- Arel.sql("pathology_current_key_observation_sets.#{column}")
35
+ def self.pathology_result_sort_predicate(column)
36
+ Arel.sql(
37
+ "cast(values -> '#{column.upcase}' ->> 'result' as float)"
38
+ )
39
+ end
40
+
41
+ def self.pathology_date_sort_predicate(column)
42
+ Arel.sql(
43
+ "cast(values -> '#{column.upcase}' ->> 'observed_at' as date)"
44
+ )
37
45
  end
38
46
  end
39
47
  end
@@ -7,14 +7,15 @@ module Renalware
7
7
  extend Enumerize
8
8
  acts_as_paranoid
9
9
  validates :patient_id, presence: true
10
- validates :hospital_unit_id, presence: true
11
- validates :hospital_ward_id, presence: true
12
10
  validates :started_on, presence: true
13
11
  validates :description, presence: true
14
12
  validates :consult_type, presence: true
13
+ validates :other_site_or_ward, presence: {
14
+ if: ->(consult){ consult.consult_site_id.blank? && consult.hospital_ward_id.blank? }
15
+ }
15
16
 
16
17
  belongs_to :patient
17
- belongs_to :hospital_unit, class_name: "Hospitals::Unit"
18
+ belongs_to :consult_site, class_name: "Admissions::ConsultSite"
18
19
  belongs_to :hospital_ward, class_name: "Hospitals::Ward"
19
20
  belongs_to :seen_by, class_name: "User"
20
21
 
@@ -0,0 +1,9 @@
1
+ require_dependency "renalware/admissions"
2
+
3
+ module Renalware
4
+ module Admissions
5
+ class ConsultSite < ApplicationRecord
6
+ validates :name, presence: true
7
+ end
8
+ end
9
+ end
@@ -2,6 +2,7 @@ module Renalware
2
2
  module HD
3
3
  class MDMPatientsQuery
4
4
  include ModalityScopes
5
+ include PatientPathologyScopes
5
6
  MODALITY_NAMES = "HD".freeze
6
7
  DEFAULT_SEARCH_PREDICATE = "hgb_date".freeze
7
8
  attr_reader :q, :relation
@@ -22,7 +23,7 @@ module Renalware
22
23
  .includes(:hd_profile)
23
24
  .extending(ModalityScopes)
24
25
  .extending(PatientPathologyScopes)
25
- .with_current_key_pathology
26
+ .with_current_pathology
26
27
  .with_current_modality_matching(MODALITY_NAMES)
27
28
  .search(q)
28
29
  end
@@ -4,6 +4,7 @@ module Renalware
4
4
  module Letters
5
5
  class DraftLetter
6
6
  include Wisper::Publisher
7
+ include LetterPathology
7
8
 
8
9
  def self.build
9
10
  new
@@ -11,7 +12,7 @@ module Renalware
11
12
 
12
13
  def call(patient, params = {})
13
14
  letter = LetterFactory.new(patient, params).build
14
- # Rails.logger.info "Drafting letter of class #{letter.class} with type #{letter.type}"
15
+ build_pathology_snapshot(patient, letter)
15
16
  letter.save!
16
17
  letter.reload
17
18
  broadcast(:draft_letter_successful, letter)
@@ -26,6 +26,7 @@ module Renalware
26
26
  source: :recipient
27
27
  has_one :signature, dependent: :destroy
28
28
  has_one :archive, foreign_key: "letter_id"
29
+ serialize :pathology_snapshot, Pathology::ObservationsJsonbSerializer
29
30
 
30
31
  accepts_nested_attributes_for :main_recipient
31
32
  accepts_nested_attributes_for :cc_recipients, reject_if: :all_blank, allow_destroy: true
@@ -14,22 +14,8 @@ module Renalware
14
14
  private
15
15
 
16
16
  def recent_pathology_results
17
- @recent_pathology_results ||= find_recent_pathology_results
18
- end
19
-
20
- def find_recent_pathology_results
21
- check_letter
22
- range = Time.zone.at(1)..letter.pathology_timestamp
23
- Renalware::Pathology::CurrentObservationsForDescriptionsQuery.new(
24
- patient: patient,
25
- descriptions: Renalware::Letters::RelevantObservationDescription.all
26
- ).call.where(observed_at: range).reject{ |obs| obs.id.nil? }
27
- end
28
-
29
- def check_letter
30
- if letter.pathology_timestamp.blank?
31
- raise ArgumentError,
32
- "letter.pathology_timestamp cannot be nil when rendering letter pathology!"
17
+ @recent_pathology_results ||= begin
18
+ letter.pathology_snapshot
33
19
  end
34
20
  end
35
21
  end
@@ -4,6 +4,7 @@ module Renalware
4
4
  module Letters
5
5
  class ReviseLetter
6
6
  include Wisper::Publisher
7
+ include LetterPathology
7
8
 
8
9
  def self.build
9
10
  new
@@ -13,6 +14,7 @@ module Renalware
13
14
  letter = patient.letters.pending.find(letter_id)
14
15
  Letter.transaction do
15
16
  letter.revise(params)
17
+ build_pathology_snapshot(patient, letter) if letter.changes.key?(:pathology_timestamp)
16
18
  letter.save!
17
19
  end
18
20
  broadcast(:revise_letter_successful, letter)
@@ -0,0 +1,35 @@
1
+ require_dependency "renalware/pathology"
2
+
3
+ module Renalware
4
+ module Pathology
5
+ # We maintain current observations for each patient in their #current_observation_set.
6
+ # CurrentObservationSet#values is a hash (stored as jsonb) where the OBX code
7
+ # is the key and the result value and observation date are themselves a hash.
8
+ # So values looks like this
9
+ # {
10
+ # "HGB": {
11
+ # "result": 123,
12
+ # observed_at: "2017-12-12 12:12:12"
13
+ # },
14
+ # "CRE": {
15
+ # ...
16
+ # }
17
+ # }
18
+ # and *always* contains the very latest pathology result for any code.
19
+ # We store all incoming OBX codes, not just a restricted list.
20
+ # Legacy data might only contain a subset of codes, so #values should not be relied on
21
+ # to cover current observations for the patients entire history, just key ones.
22
+ # When displaying or using a patient's current_observation_set the consuming code
23
+ # must filter out the codes it wants.
24
+ class CurrentObservationSet < ApplicationRecord
25
+ belongs_to :patient, class_name: "Renalware::Pathology::Patient"
26
+ validates :patient, presence: true
27
+ serialize :values, ObservationsJsonbSerializer
28
+
29
+ def values_for_codes(codes)
30
+ codes = Array(codes)
31
+ values.select{ |code, _| codes.include?(code) }
32
+ end
33
+ end
34
+ end
35
+ end
@@ -10,16 +10,18 @@ module Renalware
10
10
  def message_processed(message_payload)
11
11
  pathology_params = parse_pathology_params(message_payload)
12
12
  create_observations(pathology_params)
13
+ # Note the the current_observation_set for the patient is updated by a trigger here
13
14
  end
14
15
 
15
16
  private
16
17
 
17
18
  def parse_pathology_params(message_payload)
18
- MessageParamParser.new.parse(message_payload)
19
+ MessageParamParser.new(message_payload).parse
19
20
  end
20
21
 
21
- def create_observations(params)
22
- CreateObservations.new.call(params)
22
+ def create_observations(pathology_params)
23
+ return if pathology_params.nil? # eg patient does not exist
24
+ CreateObservations.new.call(pathology_params)
23
25
  end
24
26
  end
25
27
  end
@@ -6,18 +6,43 @@ module Renalware
6
6
  # that can be persisted by ObservationRequest.
7
7
  #
8
8
  class MessageParamParser
9
+ delegate :patient_identification, :observation_request, to: :message_payload
10
+ delegate :internal_id, to: :patient_identification
11
+ delegate :observations, to: :observation_request
12
+ alias_attribute :request, :observation_request
13
+
9
14
  # message_payload is an HL7Message (a decorator around an ::HL7::Message)
10
- def parse(message_payload)
11
- request = message_payload.observation_request
15
+ def initialize(message_payload, logger = Delayed::Worker.logger)
16
+ @message_payload = message_payload
17
+ @logger = logger
18
+ end
19
+
20
+ def parse
21
+ if renalware_patient?
22
+ build_patient_params
23
+ else
24
+ logger.warn("Did not process pathology for #{internal_id}: not a renalware patient")
25
+ nil
26
+ end
27
+ end
12
28
 
13
- observations_params = build_observations_params(request.observations)
14
- request_params = build_observation_request_params(request, observations_params)
15
- build_patient_params(message_payload.patient_identification, request_params)
29
+ def renalware_patient?
30
+ Patient.exists?(local_patient_id: internal_id)
16
31
  end
17
32
 
18
33
  private
19
34
 
20
- def build_observation_request_params(request, observations_params)
35
+ attr_reader :message_payload, :logger
36
+
37
+ def observations_params
38
+ @observations_params ||= build_observations_params
39
+ end
40
+
41
+ def request_params
42
+ @request_params ||= build_observation_request_params
43
+ end
44
+
45
+ def build_observation_request_params
21
46
  request_description = find_request_description(request.identifier)
22
47
 
23
48
  {
@@ -31,7 +56,7 @@ module Renalware
31
56
  }
32
57
  end
33
58
 
34
- def build_observations_params(observations)
59
+ def build_observations_params
35
60
  observations.map do |observation|
36
61
  observation_description = find_observation_description(observation.identifier)
37
62
 
@@ -44,9 +69,9 @@ module Renalware
44
69
  end
45
70
  end
46
71
 
47
- def build_patient_params(patient_identification, params)
48
- params.tap do |p|
49
- patient = find_patient(patient_identification.internal_id)
72
+ def build_patient_params
73
+ request_params.tap do |p|
74
+ patient = find_patient(internal_id)
50
75
  p[:patient_id] = patient.id
51
76
  end
52
77
  end