renalware-core 2.0.89 → 2.0.90
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/models/renalware/hd/profile_for_modality.rb +20 -0
- data/app/models/renalware/hd/profiles_in_date_range_query.rb +32 -0
- data/app/models/renalware/hd/revise_hd_profile.rb +3 -5
- data/app/models/renalware/modalities/description.rb +8 -0
- data/app/models/renalware/pd/regime_for_modality.rb +21 -0
- data/app/models/renalware/pd/regimes_in_date_range_query.rb +30 -0
- data/app/models/renalware/ukrdc/create_encrypted_patient_xml_files.rb +6 -1
- data/app/models/renalware/ukrdc/treatment.rb +5 -0
- data/app/models/renalware/ukrdc/treatment_timeline/generate_timeline.rb +13 -7
- data/app/models/renalware/ukrdc/treatment_timeline/generate_treatments.rb +9 -4
- data/app/models/renalware/ukrdc/treatment_timeline/generator_factory.rb +1 -1
- data/app/models/renalware/ukrdc/treatment_timeline/generic/generator.rb +45 -0
- data/app/models/renalware/ukrdc/treatment_timeline/hd/generator.rb +128 -0
- data/app/models/renalware/ukrdc/treatment_timeline/hd/modality_code_map.rb +29 -0
- data/app/models/renalware/ukrdc/treatment_timeline/hd/profile_decorator.rb +45 -0
- data/app/models/renalware/ukrdc/treatment_timeline/pd/generator.rb +119 -0
- data/app/models/renalware/ukrdc/treatment_timeline/pd/modality_code_map.rb +35 -0
- data/app/models/renalware/ukrdc/treatment_timeline/pd/regime_decorator.rb +37 -0
- data/app/models/renalware/ukrdc/treatment_timeline/prepare_tables.rb +1 -0
- data/app/presenters/renalware/hd/session_presenter.rb +6 -0
- data/app/presenters/renalware/ukrdc/patient_presenter.rb +17 -6
- data/app/views/renalware/api/ukrdc/patients/_medications.xml.builder +1 -8
- data/app/views/renalware/api/ukrdc/patients/_treatments.xml.builder +84 -45
- data/app/views/renalware/api/ukrdc/patients/procedures/_dialysis_session.xml.builder +1 -1
- data/app/views/renalware/api/ukrdc/patients/treatments/_generic.xml.builder +15 -0
- data/app/views/renalware/api/ukrdc/patients/treatments/_hd.xml.builder +30 -0
- data/app/views/renalware/api/ukrdc/patients/treatments/_pd.xml.builder +13 -0
- data/db/migrate/20190705083727_alter_ukrdc_treatments.rb +8 -0
- data/db/migrate/20190705105921_create_hd_profile_for_modalites.rb +7 -0
- data/db/migrate/20190709101610_create_pd_regime_for_modalities.rb +7 -0
- data/db/views/hd_profile_for_modalities_v01.sql +63 -0
- data/db/views/pd_regime_for_modalities_v01.sql +60 -0
- data/lib/renalware/version.rb +1 -1
- data/lib/tasks/ukrdc.rake +2 -1
- data/spec/factories/hd/profiles.rb +24 -0
- data/spec/factories/pd/pd_regimes.rb +8 -0
- data/spec/factories/ukrdc/modality_codes.rb +30 -0
- metadata +21 -9
- data/app/models/renalware/ukrdc/treatment_timeline/generators/deaths_timeline.rb +0 -17
- data/app/models/renalware/ukrdc/treatment_timeline/generators/generic_timeline.rb +0 -17
- data/app/models/renalware/ukrdc/treatment_timeline/generators/hd_timeline.rb +0 -145
- data/app/models/renalware/ukrdc/treatment_timeline/generators/low_clearance_timelinex.rb +0 -17
- data/app/models/renalware/ukrdc/treatment_timeline/generators/pd_timeline.rb +0 -18
- data/app/models/renalware/ukrdc/treatment_timeline/generators/transplants_donor_timeline.rb +0 -17
- data/app/models/renalware/ukrdc/treatment_timeline/generators/transplants_recipient_timeline.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1a92055c00589d505aae488859a5a4e9071a8d49d658c1bd1dcb1d205ad10da1
|
4
|
+
data.tar.gz: 56b74f8c39ed127a5d958d6f08fc65941a11ea37c4cd62efce78714a9350230f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7b8ddfbdb456d9a15e402755da4de84caf59c0f8fb1c00e71f55d784251d955ddb712e7d3c5af3c9d4e643fd49767c2a9511a6acccd21db77f83d2dd8c28c259
|
7
|
+
data.tar.gz: 57fbed3713860f482a2c18bcfebbba90d33a8cabad1595b754cf0445cd257755e3b369baba7a0f4c38dc4e3ecdafde9220719eb9ddf88a2639402788616812a2
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_dependency "renalware/hd"
|
4
|
+
|
5
|
+
module Renalware
|
6
|
+
module HD
|
7
|
+
# Backed by a (scenic) view this model is used to resolve the HD Profile in use or created
|
8
|
+
# just after a patient was assigned an HD modality,
|
9
|
+
# Note that this view could have been written as a SQL function or a ruby query object.
|
10
|
+
# I implemented it as a view because I would like semi technical people to be able to inspect
|
11
|
+
# the view to ascertain which HD modalities have no profile etc. Its just easier to review the
|
12
|
+
# data if it is a view.
|
13
|
+
#
|
14
|
+
# Example usage
|
15
|
+
# modality_profile = HD::ProfileForModality.where(modality_id: 1)
|
16
|
+
# modality_profile.hd_profile_id # => 123
|
17
|
+
class ProfileForModality < ApplicationRecord
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "attr_extras"
|
4
|
+
|
5
|
+
module Renalware
|
6
|
+
module HD
|
7
|
+
class ProfilesInDateRangeQuery
|
8
|
+
pattr_initialize [:patient!, :from!, :to!]
|
9
|
+
|
10
|
+
def call
|
11
|
+
scope = Renalware::HD::Profile
|
12
|
+
.with_deactivated
|
13
|
+
.order(created_at: :asc, deactivated_at: :desc)
|
14
|
+
|
15
|
+
scope
|
16
|
+
.where(conditions.merge(deactivated_at: from..to))
|
17
|
+
.or(
|
18
|
+
scope.where(conditions.merge(deactivated_at: nil))
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def conditions
|
25
|
+
{
|
26
|
+
patient_id: patient.id,
|
27
|
+
created_at: from..DateTime::Infinity.new
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -3,6 +3,8 @@
|
|
3
3
|
module Renalware
|
4
4
|
module HD
|
5
5
|
class ReviseHDProfile
|
6
|
+
attr_reader :profile, :new_profile
|
7
|
+
|
6
8
|
def initialize(profile)
|
7
9
|
raise(ArgumentError, "Cannot revise a new Profile") unless profile.persisted?
|
8
10
|
|
@@ -15,12 +17,8 @@ module Renalware
|
|
15
17
|
return false unless profile.valid?
|
16
18
|
|
17
19
|
profile.restore_attributes
|
18
|
-
profile.supersede!(params)
|
20
|
+
@new_profile = profile.supersede!(params)
|
19
21
|
end
|
20
|
-
|
21
|
-
private
|
22
|
-
|
23
|
-
attr_reader :profile
|
24
22
|
end
|
25
23
|
end
|
26
24
|
end
|
@@ -26,6 +26,14 @@ module Renalware
|
|
26
26
|
def augmented_name_for(_patient)
|
27
27
|
name
|
28
28
|
end
|
29
|
+
|
30
|
+
# For a ModalityDescription with type Renalware::HD::ModalityDescription
|
31
|
+
# this will return "HD"
|
32
|
+
def namespace
|
33
|
+
return if type.blank?
|
34
|
+
|
35
|
+
type.gsub("::", "").gsub(/^Renalware/, "").gsub(/ModalityDescription$/, "").underscore
|
36
|
+
end
|
29
37
|
end
|
30
38
|
end
|
31
39
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_dependency "renalware/pd"
|
4
|
+
|
5
|
+
module Renalware
|
6
|
+
module PD
|
7
|
+
# Backed by a (scenic) view this model is used to resolve the PD Regime in use or created
|
8
|
+
# just after a patient was assigned an PD modality,
|
9
|
+
# Note that this view could have been written as a SQL function or a ruby query object.
|
10
|
+
# I implemented it as a view because I would like semi-technical people to be able to inspect
|
11
|
+
# the view to ascertain which PD modalities have no regime. Its just easier to review the
|
12
|
+
# data if it is a view.
|
13
|
+
#
|
14
|
+
# Example usage
|
15
|
+
# modality_regime = PD::RegimeForModality.where(modality_id: 1)
|
16
|
+
# modality_regime.pd_regime_id # => 123
|
17
|
+
#
|
18
|
+
class RegimeForModality < ApplicationRecord
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "attr_extras"
|
4
|
+
|
5
|
+
module Renalware
|
6
|
+
module PD
|
7
|
+
class RegimesInDateRangeQuery
|
8
|
+
pattr_initialize [:patient!, :from!, :to!]
|
9
|
+
|
10
|
+
def call
|
11
|
+
scope = Renalware::PD::Regime.order(start_date: :asc, end_date: :desc)
|
12
|
+
|
13
|
+
scope
|
14
|
+
.where(conditions.merge(end_date: from..to))
|
15
|
+
.or(
|
16
|
+
scope.where(conditions.merge(end_date: nil))
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def conditions
|
23
|
+
{
|
24
|
+
patient_id: patient.id,
|
25
|
+
start_date: from..DateTime::Infinity.new
|
26
|
+
}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -67,9 +67,11 @@ module Renalware
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def create_patient_xml_files
|
70
|
+
count = 0
|
70
71
|
patients = ukrdc_patients_who_have_changed_since_last_send
|
71
72
|
summary.num_changed_patients = patients.count
|
72
73
|
patients.find_each do |patient|
|
74
|
+
count += 1
|
73
75
|
CreatePatientXMLFile.new(
|
74
76
|
patient: patient,
|
75
77
|
dir: paths.timestamped_xml_folder,
|
@@ -79,6 +81,9 @@ module Renalware
|
|
79
81
|
logger: logger,
|
80
82
|
force_send: force_send
|
81
83
|
).call
|
84
|
+
|
85
|
+
# Every n patients, force the garbage collector to kick in
|
86
|
+
GC.start if (count % 10).zero?
|
82
87
|
end
|
83
88
|
end
|
84
89
|
|
@@ -129,7 +134,7 @@ module Renalware
|
|
129
134
|
end
|
130
135
|
|
131
136
|
def email_recipients
|
132
|
-
Array(ENV.fetch("DAILY_REPORT_EMAIL_RECIPIENTS", "
|
137
|
+
Array(ENV.fetch("DAILY_REPORT_EMAIL_RECIPIENTS", "tim@airslie.com").split(","))
|
133
138
|
end
|
134
139
|
|
135
140
|
def export_results
|
@@ -8,6 +8,11 @@ module Renalware
|
|
8
8
|
belongs_to :patient
|
9
9
|
belongs_to :clinician, class_name: "Renalware::User"
|
10
10
|
belongs_to :modality_code
|
11
|
+
belongs_to :modality, class_name: "Modalities::Modality"
|
12
|
+
belongs_to :modality_description, class_name: "Modalities::Description"
|
13
|
+
belongs_to :hospital_unit, class_name: "Hospitals::Unit"
|
14
|
+
belongs_to :hd_profile, class_name: "HD::Profile"
|
15
|
+
belongs_to :pd_regime, class_name: "PD::Regime"
|
11
16
|
validates :patient, presence: true
|
12
17
|
validates :modality_code, presence: true
|
13
18
|
|
@@ -14,19 +14,25 @@ module Renalware
|
|
14
14
|
pattr_initialize :patient
|
15
15
|
|
16
16
|
def call
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
end
|
17
|
+
# RemapModelTableNamesToTheirPreparedEquivalents.new.call do
|
18
|
+
Rails.logger.info " Generating Treatment rows for modalities #{modality_names}"
|
19
|
+
modalities.each do |modality|
|
20
|
+
generator = GeneratorFactory.call(modality)
|
21
|
+
generator.call
|
23
22
|
end
|
23
|
+
# end
|
24
24
|
end
|
25
25
|
|
26
26
|
private
|
27
27
|
|
28
28
|
def modalities
|
29
|
-
|
29
|
+
@modalities ||= begin
|
30
|
+
patient.modalities.includes(:description).order(started_on: :asc, updated_at: :asc)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def modality_names
|
35
|
+
modalities.map { |mod| mod.description.name }.join("->")
|
30
36
|
end
|
31
37
|
end
|
32
38
|
end
|
@@ -23,18 +23,23 @@ module Renalware
|
|
23
23
|
private
|
24
24
|
|
25
25
|
def patient_scope
|
26
|
-
Renalware::Patient
|
26
|
+
Renalware::Patient
|
27
|
+
.select(:id)
|
28
|
+
.where("send_to_renalreg = true or send_to_rpv = true")
|
27
29
|
end
|
28
30
|
|
29
|
-
# rubocop:disable Rails/Output
|
31
|
+
# rubocop:disable Rails/Output, Metrics/AbcSize
|
30
32
|
def generate_treatments
|
33
|
+
PrepareTables.call
|
31
34
|
Rails.logger.info "#{patient_scope.count} patients"
|
32
|
-
patient_scope.find_each do |patient|
|
35
|
+
patient_scope.find_each.with_index do |patient, index|
|
33
36
|
print "\n#{patient.id}: "
|
34
37
|
GenerateTimeline.new(patient).call
|
38
|
+
# Start gargbage collection periodically to prevent server ram issues.
|
39
|
+
GC.start if (index % 50).zero?
|
35
40
|
end
|
36
41
|
end
|
37
|
-
# rubocop:enable Rails/Output
|
42
|
+
# rubocop:enable Rails/Output, Metrics/AbcSize
|
38
43
|
|
39
44
|
def log(msg)
|
40
45
|
Rails.logger.info(msg)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_dependency "renalware/ukrdc"
|
4
|
+
|
5
|
+
module Renalware
|
6
|
+
module UKRDC
|
7
|
+
module TreatmentTimeline
|
8
|
+
module Generic
|
9
|
+
# Handles creating a treatment record for any modality that has not been handled in a more
|
10
|
+
# specific way. Note that we only create a Treatment if we find a ukrdc_modality_code_id
|
11
|
+
# in the modality's description row in the database. Some modalities will not have that
|
12
|
+
# id so we just ignore them.
|
13
|
+
class Generator
|
14
|
+
pattr_initialize :modality
|
15
|
+
|
16
|
+
def call
|
17
|
+
create_treatment if ukrdc_modality_code.present?
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def ukrdc_modality_code
|
23
|
+
@ukrdc_modality_code ||= begin
|
24
|
+
UKRDC::ModalityCode.find_by(
|
25
|
+
id: modality.description.ukrdc_modality_code_id
|
26
|
+
)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def create_treatment
|
31
|
+
Treatment.create!(
|
32
|
+
patient: modality.patient,
|
33
|
+
clinician: modality.created_by,
|
34
|
+
started_on: modality.started_on,
|
35
|
+
modality_id: modality.id,
|
36
|
+
modality_description_id: modality.description_id,
|
37
|
+
ended_on: modality.ended_on,
|
38
|
+
modality_code: ukrdc_modality_code
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_dependency "renalware/ukrdc"
|
4
|
+
require "attr_extras"
|
5
|
+
|
6
|
+
module Renalware
|
7
|
+
module UKRDC
|
8
|
+
module TreatmentTimeline
|
9
|
+
module HD
|
10
|
+
# Generates a set of treatments based on an HD modality and any HD::Profiles
|
11
|
+
# during the span of that modality.
|
12
|
+
#
|
13
|
+
# There will be an initial treatment triggered by the modality itself, and then
|
14
|
+
# a treatment for each significant change in the HD::Profile during the period of the
|
15
|
+
# modality (ie until it ends).
|
16
|
+
#
|
17
|
+
class Generator
|
18
|
+
pattr_initialize :modality
|
19
|
+
delegate :patient, to: :modality
|
20
|
+
|
21
|
+
def call
|
22
|
+
create_treatment_from_modality
|
23
|
+
create_treatments_within_modality
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def create_treatment_from_modality
|
29
|
+
create_treatment(
|
30
|
+
hd_profile_at_start_of_modality,
|
31
|
+
modality.started_on,
|
32
|
+
modality.ended_on
|
33
|
+
)
|
34
|
+
end
|
35
|
+
|
36
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
37
|
+
def create_treatment(profile, start_date, end_date)
|
38
|
+
treatments << Treatment.create!(
|
39
|
+
patient: patient,
|
40
|
+
clinician: modality.created_by,
|
41
|
+
started_on: start_date,
|
42
|
+
modality_id: modality.id,
|
43
|
+
modality_description_id: modality.description_id,
|
44
|
+
hospital_unit: profile&.hospital_unit,
|
45
|
+
ended_on: end_date,
|
46
|
+
modality_code: ukrdc_modality_code_from_profile(profile),
|
47
|
+
hd_profile: profile
|
48
|
+
)
|
49
|
+
|
50
|
+
# Update the end date on the previous treatment - ie the one we just added is
|
51
|
+
# taking over as the currently active treatment
|
52
|
+
unless treatments.length <= 1
|
53
|
+
previous_treatment = treatments[treatments.length - 2]
|
54
|
+
previous_treatment.update!(ended_on: start_date)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
58
|
+
|
59
|
+
# Find the modality that was active on the day of the modality change
|
60
|
+
# The profile might have been added up to say 14 days later however so if there is none
|
61
|
+
# active on the day the modality was created, search up to 14 days ahead until we find
|
62
|
+
# one. Return nil if none found.
|
63
|
+
# Its complicated a bit by the fact that although there is a prescribed_on in the
|
64
|
+
# hd_profile, it is sometimes missing so we need to default to created_at in that
|
65
|
+
# instance.
|
66
|
+
def hd_profile_at_start_of_modality
|
67
|
+
@hd_profile_at_start_of_modality ||= begin
|
68
|
+
hd_profile_id = Renalware::HD::ProfileForModality.find_by!(
|
69
|
+
modality_id: modality.id
|
70
|
+
).hd_profile_id
|
71
|
+
return if hd_profile_id.blank?
|
72
|
+
|
73
|
+
HD::ProfileDecorator.new(
|
74
|
+
Renalware::HD::Profile.with_deactivated.find(hd_profile_id)
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# 3 things trigger a new Treatment for an HD patient
|
80
|
+
# - change of site
|
81
|
+
# - change of hd_type to from hd and (hdf_pre || hdf_post)
|
82
|
+
# - change of hd prescription
|
83
|
+
# Loop through the hd_profiles and trigger an new treatment when these change
|
84
|
+
# There is a problem here as we are creating duplicate treatments
|
85
|
+
# I think we need to first find the hd profile that is associated with the hd modality
|
86
|
+
# and that becomes the 'last_profile' here
|
87
|
+
def create_treatments_within_modality
|
88
|
+
last_profile = hd_profile_at_start_of_modality
|
89
|
+
|
90
|
+
hd_profiles.each do |profile_|
|
91
|
+
profile = HD::ProfileDecorator.new(profile_, last_profile: last_profile)
|
92
|
+
create_treatment_from(profile) if last_profile.nil? || profile.changed?
|
93
|
+
last_profile = profile
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def hd_profiles
|
98
|
+
profiles = Renalware::HD::ProfilesInDateRangeQuery.new(
|
99
|
+
patient: patient,
|
100
|
+
from: modality.started_on,
|
101
|
+
to: modality.ended_on
|
102
|
+
).call
|
103
|
+
profiles - Array(hd_profile_at_start_of_modality)
|
104
|
+
end
|
105
|
+
|
106
|
+
def create_treatment_from(profile)
|
107
|
+
start_date = profile.present? ? profile.created_at : modality.started_on
|
108
|
+
end_date = profile.present? ? profile.deactivated_at : modality.ended_on
|
109
|
+
|
110
|
+
create_treatment(profile, start_date, end_date)
|
111
|
+
end
|
112
|
+
|
113
|
+
def treatments
|
114
|
+
@treatments ||= []
|
115
|
+
end
|
116
|
+
|
117
|
+
def hd_patient
|
118
|
+
Renalware::HD.cast_patient(patient)
|
119
|
+
end
|
120
|
+
|
121
|
+
def ukrdc_modality_code_from_profile(profile)
|
122
|
+
ModalityCodeMap.new.code_for_profile(profile)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|