renalware-core 2.0.89 → 2.0.90

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/app/models/renalware/hd/profile_for_modality.rb +20 -0
  3. data/app/models/renalware/hd/profiles_in_date_range_query.rb +32 -0
  4. data/app/models/renalware/hd/revise_hd_profile.rb +3 -5
  5. data/app/models/renalware/modalities/description.rb +8 -0
  6. data/app/models/renalware/pd/regime_for_modality.rb +21 -0
  7. data/app/models/renalware/pd/regimes_in_date_range_query.rb +30 -0
  8. data/app/models/renalware/ukrdc/create_encrypted_patient_xml_files.rb +6 -1
  9. data/app/models/renalware/ukrdc/treatment.rb +5 -0
  10. data/app/models/renalware/ukrdc/treatment_timeline/generate_timeline.rb +13 -7
  11. data/app/models/renalware/ukrdc/treatment_timeline/generate_treatments.rb +9 -4
  12. data/app/models/renalware/ukrdc/treatment_timeline/generator_factory.rb +1 -1
  13. data/app/models/renalware/ukrdc/treatment_timeline/generic/generator.rb +45 -0
  14. data/app/models/renalware/ukrdc/treatment_timeline/hd/generator.rb +128 -0
  15. data/app/models/renalware/ukrdc/treatment_timeline/hd/modality_code_map.rb +29 -0
  16. data/app/models/renalware/ukrdc/treatment_timeline/hd/profile_decorator.rb +45 -0
  17. data/app/models/renalware/ukrdc/treatment_timeline/pd/generator.rb +119 -0
  18. data/app/models/renalware/ukrdc/treatment_timeline/pd/modality_code_map.rb +35 -0
  19. data/app/models/renalware/ukrdc/treatment_timeline/pd/regime_decorator.rb +37 -0
  20. data/app/models/renalware/ukrdc/treatment_timeline/prepare_tables.rb +1 -0
  21. data/app/presenters/renalware/hd/session_presenter.rb +6 -0
  22. data/app/presenters/renalware/ukrdc/patient_presenter.rb +17 -6
  23. data/app/views/renalware/api/ukrdc/patients/_medications.xml.builder +1 -8
  24. data/app/views/renalware/api/ukrdc/patients/_treatments.xml.builder +84 -45
  25. data/app/views/renalware/api/ukrdc/patients/procedures/_dialysis_session.xml.builder +1 -1
  26. data/app/views/renalware/api/ukrdc/patients/treatments/_generic.xml.builder +15 -0
  27. data/app/views/renalware/api/ukrdc/patients/treatments/_hd.xml.builder +30 -0
  28. data/app/views/renalware/api/ukrdc/patients/treatments/_pd.xml.builder +13 -0
  29. data/db/migrate/20190705083727_alter_ukrdc_treatments.rb +8 -0
  30. data/db/migrate/20190705105921_create_hd_profile_for_modalites.rb +7 -0
  31. data/db/migrate/20190709101610_create_pd_regime_for_modalities.rb +7 -0
  32. data/db/views/hd_profile_for_modalities_v01.sql +63 -0
  33. data/db/views/pd_regime_for_modalities_v01.sql +60 -0
  34. data/lib/renalware/version.rb +1 -1
  35. data/lib/tasks/ukrdc.rake +2 -1
  36. data/spec/factories/hd/profiles.rb +24 -0
  37. data/spec/factories/pd/pd_regimes.rb +8 -0
  38. data/spec/factories/ukrdc/modality_codes.rb +30 -0
  39. metadata +21 -9
  40. data/app/models/renalware/ukrdc/treatment_timeline/generators/deaths_timeline.rb +0 -17
  41. data/app/models/renalware/ukrdc/treatment_timeline/generators/generic_timeline.rb +0 -17
  42. data/app/models/renalware/ukrdc/treatment_timeline/generators/hd_timeline.rb +0 -145
  43. data/app/models/renalware/ukrdc/treatment_timeline/generators/low_clearance_timelinex.rb +0 -17
  44. data/app/models/renalware/ukrdc/treatment_timeline/generators/pd_timeline.rb +0 -18
  45. data/app/models/renalware/ukrdc/treatment_timeline/generators/transplants_donor_timeline.rb +0 -17
  46. 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: dea8d3ef3c91160dead8c1db92b9a131b218f2ab1001c2acd0ad6698f6b83e33
4
- data.tar.gz: 4a19dba06dfd48d8973e103dd3cb950162bf3fe038af55acbd5cee991e240b17
3
+ metadata.gz: 1a92055c00589d505aae488859a5a4e9071a8d49d658c1bd1dcb1d205ad10da1
4
+ data.tar.gz: 56b74f8c39ed127a5d958d6f08fc65941a11ea37c4cd62efce78714a9350230f
5
5
  SHA512:
6
- metadata.gz: de2b1c0d56675e5e23607cca2729c9864516ff919e838a278dcc84a15598b72ed2ab26b0be8894894a8cb10d5c1229213af78a6001bbfcc723ec881e5f43e52b
7
- data.tar.gz: a8c732dfd1caba2154487c21c8f557ba52e09f161b71886262516938b2331399c33806d9fe292b961ad08d60d373baf55e632c1754d7cddfdcebe76922d5259f
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", "dev@airslie.com").split(","))
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
- PrepareTables.call
18
- RemapModelTableNamesToTheirPreparedEquivalents.new.call do
19
- modalities.each do |modality|
20
- print "#{modality.description.name} "
21
- GeneratorFactory.call(modality).call
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
- patient.modalities.order(started_on: :asc, updated_at: :asc)
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.select(:id)
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)
@@ -25,7 +25,7 @@ module Renalware
25
25
  end
26
26
 
27
27
  def self.klass_for(type)
28
- "Renalware::UKRDC::TreatmentTimeline::Generators::#{type}Timeline".safe_constantize
28
+ "Renalware::UKRDC::TreatmentTimeline::#{type}::Generator".safe_constantize
29
29
  end
30
30
  end
31
31
  end
@@ -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