renalware-core 2.0.0.pre.rc10 → 2.0.0.pre.rc11
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.
- 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
|