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.
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