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.
- checksums.yaml +4 -4
- data/app/assets/stylesheets/renalware/modules/_dashboard.scss +5 -4
- data/app/controllers/renalware/admin/users_controller.rb +6 -2
- data/app/controllers/renalware/admissions/consults_controller.rb +1 -1
- data/app/controllers/renalware/letters/contacts_controller.rb +4 -1
- data/app/controllers/renalware/pathology/current_observation_results_controller.rb +5 -11
- data/app/controllers/renalware/research/study_participants_controller.rb +1 -1
- data/app/helpers/renalware/dashboards_helper.rb +10 -0
- data/app/helpers/renalware/layout_helper.rb +3 -1
- data/app/models/concerns/renalware/letters/letter_pathology.rb +20 -0
- data/app/models/concerns/renalware/patient_pathology_scopes.rb +15 -7
- data/app/models/renalware/admissions/consult.rb +4 -3
- data/app/models/renalware/admissions/consult_site.rb +9 -0
- data/app/models/renalware/hd/mdm_patients_query.rb +2 -1
- data/app/models/renalware/letters/draft_letter.rb +2 -1
- data/app/models/renalware/letters/letter.rb +1 -0
- data/app/models/renalware/letters/part/recent_pathology_results.rb +2 -16
- data/app/models/renalware/letters/revise_letter.rb +2 -0
- data/app/models/renalware/pathology/{current_key_observation_set.rb → current_key_observation_set.rb.dead} +0 -0
- data/app/models/renalware/pathology/current_observation_set.rb +35 -0
- data/app/models/renalware/pathology/message_listener.rb +5 -3
- data/app/models/renalware/pathology/message_param_parser.rb +35 -10
- data/app/models/renalware/pathology/observation.rb +1 -1
- data/app/models/renalware/pathology/observation_request.rb +4 -1
- data/app/models/renalware/pathology/observations_jsonb_serializer.rb +73 -0
- data/app/models/renalware/pathology/patient.rb +4 -0
- data/app/models/renalware/pathology/update_current_observations.rb.dead +25 -0
- data/app/models/renalware/patient.rb +1 -1
- data/app/models/renalware/patients/mdm_patients_query.rb +4 -8
- data/app/models/renalware/pd/mdm_patients_query.rb +4 -8
- data/app/models/renalware/renal/low_clearance/mdm_patients_query.rb +5 -4
- data/app/models/renalware/transplants/mdm_patients_query.rb +4 -1
- data/app/presenters/renalware/admissions/consult_presenter.rb +6 -2
- data/app/presenters/renalware/dashboard/dashboard_presenter.rb +0 -4
- data/app/presenters/renalware/hd/patient_presenter.rb +6 -0
- data/app/presenters/renalware/mdm_patient_presenter.rb +6 -10
- data/app/presenters/renalware/pathology/{current_observation_results/html_table_view.rb → current_observation_results.dead/html_table_view.rb.dead} +0 -0
- data/app/presenters/renalware/pathology/{current_observation_results/presenter.rb → current_observation_results.dead/presenter.rb.dead} +0 -0
- data/app/presenters/renalware/pathology/observation_set_presenter.rb +57 -0
- data/app/presenters/renalware/pathology/observations_diff.rb +97 -77
- data/app/presenters/renalware/pathology/patient_presenter.rb +0 -4
- data/app/presenters/renalware/renal/clinical_summary_presenter.rb +1 -1
- data/app/views/renalware/admin/users/_filters.html.slim +19 -0
- data/app/views/renalware/admin/users/index.html.slim +9 -12
- data/app/views/renalware/admissions/consults/_filters.html.slim +2 -2
- data/app/views/renalware/admissions/consults/_form.html.slim +13 -53
- data/app/views/renalware/admissions/consults/_table.html.slim +3 -3
- data/app/views/renalware/dashboard/dashboards/show.html.slim +1 -1
- data/app/views/renalware/hd/mdm_patients/_patient.html.slim +2 -2
- data/app/views/renalware/letters/letters/_pathology.html.slim +2 -2
- data/app/views/renalware/letters/parts/_recent_pathology_results.html.slim +3 -3
- data/app/views/renalware/mdm_patients/_patient.html.slim +7 -7
- data/app/views/renalware/pathology/current_observation_results/index.html.slim +12 -1
- data/app/views/renalware/pathology/observations/_diff.html.slim +26 -20
- data/config/initializers/core_extensions.rb +2 -0
- data/config/locales/renalware/admissions/consults.en.yml +9 -0
- data/config/locales/renalware/dashboard/dashboard.yml +1 -1
- data/config/locales/renalware/patient.yml +0 -1
- data/db/migrate/20171204112150_create_consult_sites.rb +14 -0
- data/db/migrate/20171211161400_create_pathology_current_table.rb +19 -0
- data/db/migrate/20171213111513_create_fn_to_refresh_current_obs.rb +50 -0
- data/db/migrate/20171214141335_create_trigger_to_update_current_observation_sets.rb +111 -0
- data/db/migrate/20171214190849_enforce_request_id_on_observations.rb +5 -0
- data/db/migrate/20171215122454_add_pathology_observation_set_to_letters.rb +10 -0
- data/lib/core_extensions/hash.rb +11 -0
- data/lib/renalware/version.rb +1 -1
- data/spec/factories/admissions/consult_sites.rb +6 -0
- data/spec/factories/admissions/consults.rb +1 -1
- metadata +21 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c3c267233dd81ec4b3886e524d833d785dc3a371
|
4
|
+
data.tar.gz: cdcad7175c0fe8258a43f4dced4e0d3f159683bc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
:
|
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(
|
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
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
@@ -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
|
10
|
-
includes(:
|
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) {
|
27
|
-
base.ransacker(:"#{code}_date") {
|
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) {
|
31
|
+
base.ransacker(code) { pathology_result_sort_predicate(code) }
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
-
def self.
|
36
|
-
Arel.sql(
|
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 :
|
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
|
|
@@ -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
|
-
.
|
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
|
-
|
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 ||=
|
18
|
-
|
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)
|
File without changes
|
@@ -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
|
19
|
+
MessageParamParser.new(message_payload).parse
|
19
20
|
end
|
20
21
|
|
21
|
-
def create_observations(
|
22
|
-
|
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
|
11
|
-
|
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
|
-
|
14
|
-
|
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
|
-
|
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
|
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
|
48
|
-
|
49
|
-
patient = find_patient(
|
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
|