renalware-core 2.0.0.pre.rc10 → 2.0.0.pre.rc11
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 +12 -3
- data/app/assets/stylesheets/renalware/modules/_letters.scss +0 -6
- data/app/assets/stylesheets/renalware/modules/_pathology.scss +5 -0
- data/app/assets/stylesheets/renalware/modules/_patients.scss +24 -0
- data/app/assets/stylesheets/renalware/modules/_users.scss +36 -0
- data/app/controllers/renalware/admin/cache_controller.rb +17 -0
- data/app/controllers/renalware/admin/users_controller.rb +1 -0
- data/app/controllers/renalware/api/token_authenticated_api_controller.rb +25 -0
- data/app/controllers/renalware/api/v1/patients/patients_controller.rb +17 -0
- data/app/controllers/renalware/concerns/devise_controller_methods.rb +4 -1
- data/app/controllers/renalware/devise/sessions_controller.rb +0 -29
- data/app/controllers/renalware/pathology/historical_observation_results_controller.rb +8 -10
- data/app/controllers/renalware/pathology/recent_observation_results_controller.rb +8 -10
- data/app/controllers/renalware/renal/aki_alerts_controller.rb +6 -1
- data/app/controllers/renalware/reporting/audits_controller.rb +1 -1
- data/app/controllers/renalware/system/errors_controller.rb +3 -1
- data/app/controllers/renalware/transplants/wait_lists_controller.rb +12 -3
- data/app/models/renalware/admin.rb +4 -0
- data/app/models/renalware/api.rb +6 -0
- data/app/models/renalware/clinics/current_observations.rb +1 -0
- data/app/models/renalware/events/event_query.rb +1 -1
- data/app/models/renalware/feeds/hl7_message.rb +16 -1
- data/app/models/renalware/hd/mdm_patients_query.rb +1 -1
- data/app/models/renalware/letters/pdf_letter_cache.rb +5 -1
- data/app/models/renalware/medications/prescription.rb +1 -0
- data/app/models/renalware/pathology/create_observations_grouped_by_date_table.rb +39 -0
- data/app/models/renalware/pathology/observation.rb +1 -1
- data/app/models/renalware/pathology/observation_digest.rb +12 -0
- data/app/models/renalware/pathology/observation_requests_attributes_builder.rb +2 -1
- data/app/models/renalware/pathology/observations_grouped_by_date_query.rb +91 -0
- data/app/models/renalware/pathology/observations_grouped_by_date_table.rb +59 -0
- data/app/models/renalware/pd/mdm_patients_query.rb +3 -1
- data/app/models/renalware/renal/aki_alert.rb +1 -0
- data/app/models/renalware/reporting/audit.rb +2 -0
- data/app/models/renalware/system/update_user.rb +0 -1
- data/app/models/renalware/transplants/registrations/wait_list_query.rb +9 -5
- data/app/models/renalware/transplants.rb +2 -0
- data/app/models/renalware/user.rb +26 -8
- data/app/policies/renalware/admin/cache_policy.rb +15 -0
- data/app/presenters/renalware/admin/users/summary_part.rb +23 -0
- data/app/presenters/renalware/events/summary_part.rb +15 -8
- data/app/presenters/renalware/hd/mdm_presenter.rb +1 -1
- data/app/presenters/renalware/letters/summary_part.rb +4 -4
- data/app/presenters/renalware/mdm_presenter.rb +18 -9
- data/app/presenters/renalware/pathology/historical_observation_results/html_table_view.rb +15 -1
- data/app/presenters/renalware/problems/summary_part.rb +4 -6
- data/app/presenters/renalware/summary_part.rb +5 -4
- data/app/views/renalware/admin/cache/show.html.slim +20 -0
- data/app/views/renalware/admin/feeds/files/index.html.slim +0 -1
- data/app/views/renalware/admin/users/_summary_part.html.slim +3 -0
- data/app/views/renalware/admin/users/index.html.slim +25 -8
- data/app/views/renalware/admissions/_summary_part.html.slim +11 -12
- data/app/views/renalware/api/v1/patients/patients/show.json.jbuilder +17 -0
- data/app/views/renalware/dashboard/dashboards/_content.html.slim +3 -0
- data/app/views/renalware/devise/registrations/edit.html.slim +2 -0
- data/app/views/renalware/events/events/_summary_part.html.slim +4 -5
- data/app/views/renalware/letters/_summary_part.html.slim +15 -14
- data/app/views/renalware/letters/letters/_table.html.slim +2 -1
- data/app/views/renalware/mdm/_pathology.html.slim +4 -2
- data/app/views/renalware/medications/_summary_part.html.slim +1 -1
- data/app/views/renalware/navigation/_renal.html.slim +1 -1
- data/app/views/renalware/navigation/_renalware_admin.html.slim +1 -0
- data/app/views/renalware/pathology/_navigation.html.slim +1 -1
- data/app/views/renalware/pathology/historical_observation_results/_table.html.slim +13 -0
- data/app/views/renalware/pathology/historical_observation_results/index.html.slim +3 -3
- data/app/views/renalware/pathology/observation_requests/_table.html.slim +1 -1
- data/app/views/renalware/pathology/recent_observation_results/_table.html.slim +18 -0
- data/app/views/renalware/pathology/recent_observation_results/index.html.slim +3 -3
- data/app/views/renalware/patients/clinical_summaries/show.html.slim +7 -4
- data/app/views/renalware/patients/side_menu/_actions.html.slim +26 -21
- data/app/views/renalware/problems/problems/_problem.html.slim +3 -0
- data/app/views/renalware/problems/problems/_summary_part.html.slim +16 -5
- data/app/views/renalware/renal/aki_alerts/edit.html.slim +4 -0
- data/app/views/renalware/renal/aki_alerts/index.html.slim +8 -0
- data/app/views/renalware/transplants/mdm/_pathology_cmvdna.html.slim +3 -1
- data/app/views/renalware/transplants/wait_lists/show.html.slim +2 -2
- data/config/initializers/inflections.rb +1 -0
- data/config/locales/renalware/mdm.yml +2 -2
- data/config/locales/renalware/renal/aki_alerts.en.yml +4 -0
- data/config/routes.rb +22 -5
- data/config/{schedule.rb → schedule.rb.example} +0 -0
- data/db/functions/update_current_observation_set_from_trigger_v03.sql +93 -0
- data/db/functions/update_current_observation_set_from_trigger_v04.sql +93 -0
- data/db/migrate/20180202184954_create_view_pathology_observation_digests.rb +5 -0
- data/db/migrate/20180206225525_update_fn_update_current_observation_set_from_trigger.rb +9 -0
- data/db/migrate/20180208150629_add_authentication_token_to_users.rb +5 -0
- data/db/migrate/20180213124203_add_cancelled_to_pathology_observations.rb +9 -0
- data/db/migrate/20180213125734_update_fn_update_current_obs_set_trgger.rb +9 -0
- data/db/migrate/20180214124317_add_cols_to_aki_alerts.rb +9 -0
- data/db/migrate/20180216132741_disable_some_audits.rb +8 -0
- data/db/seeds/default/transplants/transplant_donor_stages.rb +2 -2
- data/db/views/pathology_observation_digests_v01.sql +20 -0
- data/lib/renalware/version.rb +1 -1
- data/spec/factories/pathology/observation_requests.rb +31 -0
- data/spec/support/login_macros.rb +4 -2
- metadata +37 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 57dc3391a5faf70954e6f6ce4e04ccfa81b4089bcccd51c9b1e8f826554325e8
|
4
|
+
data.tar.gz: 153f6ce8fd7006f45d9d1a2349c0d365edcfb0effe190457c808420c921fd93f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0473fadd4cee90562f9bb1cf86a77510fd7642fb3a25be9aff729d9b3e02ed76064eb0ee213852879c0cfabb00a819b7f6a3f3931ea26fbc26539a056a2063ec
|
7
|
+
data.tar.gz: 8807fe96b88c7930d1b931fb81c4571394538d7759b27c94695157b2956033063da7a9176976e977c48632ba47391a2d32f43fcf44f85c2e52b5b01e56f2775b
|
@@ -1,10 +1,19 @@
|
|
1
1
|
.dashboard-content {
|
2
2
|
margin-top: 10px;
|
3
3
|
|
4
|
-
|
4
|
+
h3 {
|
5
|
+
font-size: 1.1rem;
|
6
|
+
font-weight: bold;
|
7
|
+
}
|
8
|
+
|
9
|
+
p {
|
5
10
|
padding-left: 0;
|
6
|
-
|
7
|
-
|
11
|
+
|
12
|
+
&.empty-section {
|
13
|
+
padding-left: 0;
|
14
|
+
color: $dashboard-muted-color;
|
15
|
+
clear: both;
|
16
|
+
}
|
8
17
|
}
|
9
18
|
}
|
10
19
|
|
@@ -377,3 +377,27 @@ form {
|
|
377
377
|
}
|
378
378
|
}
|
379
379
|
}
|
380
|
+
|
381
|
+
.page--clinical_summaries {
|
382
|
+
.summary-part--letters {
|
383
|
+
@include grid-column(12);
|
384
|
+
}
|
385
|
+
|
386
|
+
.summary-part--problems,
|
387
|
+
.summary-part--prescriptions {
|
388
|
+
@include grid-column(12);
|
389
|
+
|
390
|
+
@media #{$large-up} {
|
391
|
+
@include grid-column(6);
|
392
|
+
}
|
393
|
+
}
|
394
|
+
|
395
|
+
.summary-part--events,
|
396
|
+
.summary-part--admissions {
|
397
|
+
@include grid-column(12);
|
398
|
+
|
399
|
+
@media #{$xxlarge-up} {
|
400
|
+
@include grid-column(6);
|
401
|
+
}
|
402
|
+
}
|
403
|
+
}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
.admin-users {
|
2
|
+
td.approved {
|
3
|
+
text-align: center;
|
4
|
+
color: $nhs-light-green;
|
5
|
+
|
6
|
+
.unapproved {
|
7
|
+
color: $nhs-red;
|
8
|
+
font-weight: bold;
|
9
|
+
}
|
10
|
+
}
|
11
|
+
|
12
|
+
.tag {
|
13
|
+
color: $white;
|
14
|
+
padding: .05rem .5rem .15rem .5rem;
|
15
|
+
border: none;
|
16
|
+
margin: .1rem .3rem .2rem 0;
|
17
|
+
border-radius: .3rem;
|
18
|
+
background-color: $mid-grey;
|
19
|
+
|
20
|
+
&.devops {
|
21
|
+
|
22
|
+
}
|
23
|
+
|
24
|
+
&.clinical {
|
25
|
+
background-color: $nhs-green;
|
26
|
+
}
|
27
|
+
|
28
|
+
&.admin {
|
29
|
+
background-color: $nhs-blue;
|
30
|
+
}
|
31
|
+
|
32
|
+
&.super_admin {
|
33
|
+
background-color: $nhs-red;
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_dependency "renalware/admin"
|
2
|
+
|
3
|
+
module Renalware
|
4
|
+
module Admin
|
5
|
+
class CacheController < BaseController
|
6
|
+
def show
|
7
|
+
authorize [:renalware, :admin, :cache], :show?
|
8
|
+
end
|
9
|
+
|
10
|
+
def destroy
|
11
|
+
authorize [:renalware, :admin, :cache], :destroy?
|
12
|
+
Rails.cache.clear
|
13
|
+
redirect_to admin_cache_path, notice: "Cache successfully cleared"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
#
|
2
|
+
# From https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
|
3
|
+
#
|
4
|
+
module Renalware
|
5
|
+
module API
|
6
|
+
class TokenAuthenticatedApiController < ApplicationController
|
7
|
+
before_action :authenticate_user_from_token!
|
8
|
+
before_action :authenticate_user! # fallback
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def authenticate_user_from_token!
|
13
|
+
username = params[:username].presence
|
14
|
+
user = username && User.find_by(username: username)
|
15
|
+
|
16
|
+
# Notice how we use Devise.secure_compare to compare the token
|
17
|
+
# in the database with the token given in the params, mitigating
|
18
|
+
# timing attacks.
|
19
|
+
if user && Devise.secure_compare(user.authentication_token, params[:token])
|
20
|
+
sign_in user, store: false
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require_dependency "renalware/patients"
|
2
|
+
require_dependency "renalware/api"
|
3
|
+
|
4
|
+
module Renalware
|
5
|
+
module API
|
6
|
+
module V1
|
7
|
+
module Patients
|
8
|
+
class PatientsController < TokenAuthenticatedApiController
|
9
|
+
def show
|
10
|
+
patient = Patient.find_by!(local_patient_id: params[:id])
|
11
|
+
render locals: { patient: patient }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -37,6 +37,7 @@ module Renalware
|
|
37
37
|
devise_parameter_sanitizer.permit(:sign_in, keys: [:username, :password, :remember_me])
|
38
38
|
end
|
39
39
|
|
40
|
+
# rubocop:disable Metrics/MethodLength
|
40
41
|
def configure_account_update_parameters
|
41
42
|
devise_parameter_sanitizer.permit(
|
42
43
|
:account_update,
|
@@ -49,10 +50,12 @@ module Renalware
|
|
49
50
|
:password_confirmation,
|
50
51
|
:current_password,
|
51
52
|
:professional_position,
|
52
|
-
:signature
|
53
|
+
:signature,
|
54
|
+
:with_extended_validation
|
53
55
|
]
|
54
56
|
)
|
55
57
|
end
|
58
|
+
# rubocop:enable Metrics/MethodLength
|
56
59
|
end
|
57
60
|
end
|
58
61
|
end
|
@@ -17,35 +17,6 @@ module Renalware
|
|
17
17
|
max_duration_has_passed ? dashboard_path : super
|
18
18
|
end
|
19
19
|
|
20
|
-
# Important note: Since Devise 4.4.0 the gem's SessionsController#create will do an implicit
|
21
|
-
# check to see if the resource (User signing-in) is valid (or rather, I think it checks for the
|
22
|
-
# presence of anything in resource.errors). If the user is not valid it now
|
23
|
-
# skips the redirect to the path specified in .after_sign_in_path_for.
|
24
|
-
# The result for us was that the user _could_ log on, but they stayed on the login screen
|
25
|
-
# (the appearance of the menu indicated they were in fact logged in). The redirect was not
|
26
|
-
# happening because the user was invalid (ie there were validation errors and
|
27
|
-
# user.valid? == false).
|
28
|
-
# The reason for the validation errors is that we have some conditional validation in User;
|
29
|
-
# for instance only validate #signature during an update, not on
|
30
|
-
# a create - after all #signature is something they set up later in their 'profile'.
|
31
|
-
# So what we have to do here is stop Devise from thinking the user in invalid by skipping
|
32
|
-
# validations.
|
33
|
-
# See https://github.com/plataformatec/devise/issues/4742#issuecomment-355154023
|
34
|
-
# Note that ideally we should move signature, professional position etc to a Profile model
|
35
|
-
# and then we could remove the conditional validation.
|
36
|
-
def create
|
37
|
-
super do |resource|
|
38
|
-
# This first line clears any existing errors that prevent Devise redirecting on successful
|
39
|
-
# login. It does not negate any password validations however. Its just a workaround to get
|
40
|
-
# the redirect to work.
|
41
|
-
resource.errors.clear
|
42
|
-
# This second line means resource.valid? returns true ie conditional on: :update validations
|
43
|
-
# are skipped. This line is not necessary (clearing the error above actually solves the
|
44
|
-
# problem) but does no harm and is belt and braces.
|
45
|
-
resource.skip_validation = true
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
20
|
private
|
50
21
|
|
51
22
|
def last_sign_in_at
|
@@ -3,19 +3,17 @@ require_dependency "renalware/pathology"
|
|
3
3
|
module Renalware
|
4
4
|
module Pathology
|
5
5
|
class HistoricalObservationResultsController < Pathology::BaseController
|
6
|
+
include Renalware::Concerns::Pageable
|
6
7
|
before_action :load_patient
|
7
8
|
|
8
9
|
def index
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
paginator: presenter.paginator,
|
17
|
-
table: table_view
|
18
|
-
}
|
10
|
+
observations_table = CreateObservationsGroupedByDateTable.new(
|
11
|
+
patient: patient,
|
12
|
+
observation_descriptions: RelevantObservationDescription.all,
|
13
|
+
page: page || 1,
|
14
|
+
per_page: 25
|
15
|
+
).call
|
16
|
+
render :index, locals: { table: observations_table }
|
19
17
|
end
|
20
18
|
end
|
21
19
|
end
|
@@ -3,19 +3,17 @@ require_dependency "renalware/pathology"
|
|
3
3
|
module Renalware
|
4
4
|
module Pathology
|
5
5
|
class RecentObservationResultsController < Pathology::BaseController
|
6
|
+
include Renalware::Concerns::Pageable
|
6
7
|
before_action :load_patient
|
7
8
|
|
8
9
|
def index
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
paginator: presenter.paginator,
|
17
|
-
table: table_view
|
18
|
-
}
|
10
|
+
observations_table = CreateObservationsGroupedByDateTable.new(
|
11
|
+
patient: patient,
|
12
|
+
observation_descriptions: RelevantObservationDescription.all,
|
13
|
+
page: page || 1,
|
14
|
+
per_page: per_page || 100
|
15
|
+
).call
|
16
|
+
render :index, locals: { table: observations_table }
|
19
17
|
end
|
20
18
|
end
|
21
19
|
end
|
@@ -37,7 +37,12 @@ module Renalware
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def aki_alert_params
|
40
|
-
params
|
40
|
+
params
|
41
|
+
.require(:renal_aki_alert)
|
42
|
+
.permit(
|
43
|
+
:notes, :action_id, :hotlist, :hospital_ward_id,
|
44
|
+
:max_cre, :cre_date, :max_aki, :aki_date
|
45
|
+
)
|
41
46
|
end
|
42
47
|
end
|
43
48
|
end
|
@@ -13,7 +13,9 @@ module Renalware
|
|
13
13
|
|
14
14
|
def generate_test_internal_server_error
|
15
15
|
raise "This is an intentionally raised error - please ignore it. " \
|
16
|
-
"It is used only to test system integration"
|
16
|
+
"It is used only to test system integration. " \
|
17
|
+
"The rest of this messages is padding to test that the title is truncated to 256 " \
|
18
|
+
"characters#{'.' * 100}"
|
17
19
|
end
|
18
20
|
end
|
19
21
|
end
|
@@ -6,19 +6,28 @@ module Renalware
|
|
6
6
|
include Renalware::Concerns::Pageable
|
7
7
|
|
8
8
|
def show
|
9
|
-
query = Registrations::WaitListQuery.new(quick_filter: params[:filter], q: params[:q])
|
10
9
|
registrations = query.call.page(page).per(per_page || 50)
|
11
10
|
authorize registrations
|
12
11
|
render locals: {
|
13
12
|
path_params: path_params,
|
14
13
|
registrations: registrations,
|
15
|
-
q: query.search
|
14
|
+
q: query.search
|
15
|
+
}
|
16
16
|
end
|
17
17
|
|
18
18
|
private
|
19
19
|
|
20
|
+
def query
|
21
|
+
@query ||= begin
|
22
|
+
Registrations::WaitListQuery.new(
|
23
|
+
named_filter: params[:named_filter],
|
24
|
+
q: params[:q]
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
20
29
|
def path_params
|
21
|
-
params.permit([:controller, :action, :
|
30
|
+
params.permit([:controller, :action, :named_filter])
|
22
31
|
end
|
23
32
|
end
|
24
33
|
end
|
@@ -51,6 +51,7 @@ module Renalware
|
|
51
51
|
end
|
52
52
|
|
53
53
|
class Observation < SimpleDelegator
|
54
|
+
attr_reader :cancelled
|
54
55
|
alias_attribute :date_time, :observation_date
|
55
56
|
alias_attribute :value, :observation_value
|
56
57
|
|
@@ -60,7 +61,21 @@ module Renalware
|
|
60
61
|
|
61
62
|
# TODO: Implement comment extraction
|
62
63
|
def comment
|
63
|
-
""
|
64
|
+
@comment || ""
|
65
|
+
end
|
66
|
+
|
67
|
+
# Some messages may come through with result text like
|
68
|
+
# ##TEST CANCELLED## Insufficient specimen received
|
69
|
+
# in which case replace with something more concise.
|
70
|
+
# We could save the actual message somewhere
|
71
|
+
def observation_value
|
72
|
+
if super.upcase.at("CANCELLED")
|
73
|
+
@comment = super
|
74
|
+
@cancelled = true
|
75
|
+
""
|
76
|
+
else
|
77
|
+
super
|
78
|
+
end
|
64
79
|
end
|
65
80
|
|
66
81
|
# Because some units of measurement, such as 10^12/L for WBC, contain a caret, the caret
|
@@ -4,7 +4,7 @@ module Renalware
|
|
4
4
|
include ModalityScopes
|
5
5
|
include PatientPathologyScopes
|
6
6
|
MODALITY_NAMES = "HD".freeze
|
7
|
-
DEFAULT_SEARCH_PREDICATE = "hgb_date".freeze
|
7
|
+
DEFAULT_SEARCH_PREDICATE = "hgb_date desc".freeze
|
8
8
|
attr_reader :q, :relation
|
9
9
|
|
10
10
|
def initialize(relation: HD::Patient.all, q:)
|
@@ -36,8 +36,12 @@ module Renalware
|
|
36
36
|
end
|
37
37
|
|
38
38
|
# Note the letter must be a LetterPresenter which has a #to_html method
|
39
|
+
# The to_html method should (and does on the LetterPrsenter class) render the complete
|
40
|
+
# html including surrounding layout with inline css and images. This way if the layout changes
|
41
|
+
# or the image is changed for example, the cache for the pdf is no longer valid and a new
|
42
|
+
# key and cache entry will be created.
|
39
43
|
def self.cache_key_for(letter)
|
40
|
-
"
|
44
|
+
"letter:pdf:#{letter.id}:#{Digest::MD5.hexdigest(letter.to_html)}"
|
41
45
|
end
|
42
46
|
end
|
43
47
|
end
|
@@ -47,6 +47,7 @@ module Renalware
|
|
47
47
|
scope :ordered, -> { includes(:drug).order("drugs.name") }
|
48
48
|
scope :with_medication_route, -> { includes(:medication_route) }
|
49
49
|
scope :with_drugs, -> { includes(drug: :drug_types) }
|
50
|
+
scope :with_classifications, -> { includes(drug: :classifications) }
|
50
51
|
scope :with_termination, -> { includes(termination: [:created_by]) }
|
51
52
|
scope :current, lambda { |date = Date.current|
|
52
53
|
eager_load(:termination)
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require_dependency "renalware/pathology"
|
2
|
+
|
3
|
+
module Renalware
|
4
|
+
module Pathology
|
5
|
+
class CreateObservationsGroupedByDateTable
|
6
|
+
attr_reader :patient, :observation_descriptions, :options
|
7
|
+
|
8
|
+
def initialize(patient:, observation_descriptions:, **options)
|
9
|
+
@patient = patient
|
10
|
+
@observation_descriptions = observation_descriptions
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def call
|
15
|
+
if observation_descriptions.blank?
|
16
|
+
raise(ArgumentError, "No observation_descriptions supplied")
|
17
|
+
end
|
18
|
+
create_observations_table
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def fetch_grouped_observations
|
24
|
+
ObservationsGroupedByDateQuery.new(
|
25
|
+
patient: patient,
|
26
|
+
observation_descriptions: observation_descriptions,
|
27
|
+
**options
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_observations_table
|
32
|
+
ObservationsGroupedByDateTable.new(
|
33
|
+
relation: fetch_grouped_observations,
|
34
|
+
observation_descriptions: observation_descriptions
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -7,7 +7,7 @@ module Renalware
|
|
7
7
|
belongs_to :description, class_name: "ObservationDescription"
|
8
8
|
|
9
9
|
validates :description, presence: true
|
10
|
-
validates :result, presence: true
|
10
|
+
validates :result, presence: true, unless: ->(obs) { obs.cancelled? }
|
11
11
|
validates :observed_at, presence: true
|
12
12
|
|
13
13
|
scope :ordered, -> { order(observed_at: :desc) }
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require_dependency "renalware/pathology"
|
2
|
+
|
3
|
+
module Renalware
|
4
|
+
module Pathology
|
5
|
+
# Backed by a view, returns pathology results grouped by the day the observation
|
6
|
+
# was made. Please check if this is used in code - it may not be,
|
7
|
+
# However the underlying view is a useful way of investigating a patient's results
|
8
|
+
# so please do not remove the view.
|
9
|
+
class ObservationDigest < ApplicationRecord
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
@@ -76,7 +76,8 @@ module Renalware
|
|
76
76
|
description_id: observation_description.id,
|
77
77
|
observed_at: parse_time(observation.date_time),
|
78
78
|
result: observation.value,
|
79
|
-
comment: observation.comment
|
79
|
+
comment: observation.comment,
|
80
|
+
cancelled: observation.cancelled
|
80
81
|
}
|
81
82
|
end.compact
|
82
83
|
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
require_dependency "renalware/pathology"
|
2
|
+
require "attr_extras"
|
3
|
+
|
4
|
+
module Renalware
|
5
|
+
module Pathology
|
6
|
+
# A custom relation-like object, implementing a kaminiari-like pagination interface.
|
7
|
+
# Its a query object but means to be used like a relation. If passed into a view you can
|
8
|
+
# do = paginate(relation).
|
9
|
+
# See ObservationsGroupedByDateTable for intended usage.
|
10
|
+
#
|
11
|
+
# .all() returns a jsonb hash of OBX results for each day a patient had an observation.
|
12
|
+
# Only returns observations whose code matches observation_descriptions
|
13
|
+
#
|
14
|
+
# Example usage:
|
15
|
+
# observation_descriptions = ..
|
16
|
+
# rows = ObservationsGroupedByDateQuery.new(
|
17
|
+
# patient: patient,
|
18
|
+
# observation_descriptions: observation_descriptions,
|
19
|
+
# per_page: 50,
|
20
|
+
# page: 1
|
21
|
+
# )
|
22
|
+
#
|
23
|
+
# Example output:
|
24
|
+
# patient_id observation_date observations
|
25
|
+
# ------------------------------------------
|
26
|
+
# 1 2018-02-02 {"CYA": "14"}
|
27
|
+
# 1 2016-06-15 {"CMVDNA": "0.10"}
|
28
|
+
# 1 2016-03-15 {"NA": "137", "TP": "74", "ALB": "48", "ALP": "71", ...
|
29
|
+
# 1 2016-02-29 {"NA": "136", "TP": "78", "ALB": "47", "ALP": "71", ...
|
30
|
+
#
|
31
|
+
#
|
32
|
+
class ObservationsGroupedByDateQuery
|
33
|
+
attr_reader :patient, :observation_descriptions, :page, :limit
|
34
|
+
alias :current_page :page
|
35
|
+
alias :limit_value :limit
|
36
|
+
|
37
|
+
def initialize(patient:, observation_descriptions:, page: 1, per_page: 50)
|
38
|
+
@patient = patient
|
39
|
+
@observation_descriptions = observation_descriptions
|
40
|
+
@page = Integer(page)
|
41
|
+
@limit = Integer(per_page)
|
42
|
+
end
|
43
|
+
|
44
|
+
def total_pages
|
45
|
+
result = conn.execute(to_count_sql)
|
46
|
+
total = result.getvalue(0, 0)
|
47
|
+
(total.to_f / limit).ceil
|
48
|
+
end
|
49
|
+
|
50
|
+
def offset
|
51
|
+
(page - 1) * limit
|
52
|
+
end
|
53
|
+
|
54
|
+
def all
|
55
|
+
conn.execute(to_paginated_sql)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def to_sql
|
61
|
+
<<-SQL.squish
|
62
|
+
select obs_req.patient_id, cast(observed_at as date) as observed_on,
|
63
|
+
jsonb_object_agg(obs_desc.code, obs.result) results
|
64
|
+
from pathology_observations obs
|
65
|
+
inner join pathology_observation_requests obs_req on obs.request_id = obs_req.id
|
66
|
+
inner join pathology_observation_descriptions obs_desc on obs.description_id = obs_desc.id
|
67
|
+
where patient_id = #{conn.quote(patient.id)}
|
68
|
+
and obs.description_id in (#{observation_description_ids})
|
69
|
+
group by patient_id, observed_on
|
70
|
+
order by patient_id asc, observed_on desc
|
71
|
+
SQL
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_count_sql
|
75
|
+
"select count(*) from (#{to_sql}) as query"
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_paginated_sql
|
79
|
+
to_sql + " limit #{limit} offset #{offset}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def conn
|
83
|
+
ActiveRecord::Base.connection
|
84
|
+
end
|
85
|
+
|
86
|
+
def observation_description_ids
|
87
|
+
observation_descriptions.map(&:id).join(",")
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|