renalware-core 2.0.0.pre.rc8 → 2.0.0.pre.rc9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/stylesheets/renalware/modules/_clinical.scss +73 -0
  3. data/app/models/concerns/renalware/clinics/most_recent_measurement_scopes.rb.dead +43 -0
  4. data/app/models/renalware/clinics/clinic_visit.rb +2 -2
  5. data/app/models/renalware/clinics/current_observations.rb +57 -0
  6. data/app/models/renalware/letters/letter.rb +5 -1
  7. data/app/models/renalware/pathology/current_observation_set.rb +6 -0
  8. data/app/models/renalware/pathology/observation.rb +1 -0
  9. data/app/models/renalware/pathology/observation_request.rb +3 -0
  10. data/app/models/renalware/pathology/observations_jsonb_serializer.rb +7 -6
  11. data/app/models/renalware/pathology/{view_current_observation_results.rb → view_current_observation_results.rb.dead} +0 -0
  12. data/app/presenters/renalware/clinical/header_presenter.rb +33 -0
  13. data/app/presenters/renalware/letters/letter_presenter.rb +5 -1
  14. data/app/presenters/renalware/letters/part_class_filter.rb +36 -0
  15. data/app/presenters/renalware/renal/clinical_summary_presenter.rb +0 -1
  16. data/app/presenters/renalware/ukrdc/patient_presenter.rb +8 -2
  17. data/app/values/renalware/bmi.rb +20 -0
  18. data/app/views/renalware/admissions/consults/_table.html.slim +1 -1
  19. data/app/views/renalware/api/ukrdc/patients/_clinic_visit_observation.xml.builder +1 -1
  20. data/app/views/renalware/api/ukrdc/patients/_lab_orders.xml.builder +3 -3
  21. data/app/views/renalware/api/ukrdc/patients/_observations.xml.builder +3 -1
  22. data/app/views/renalware/api/ukrdc/patients/lab_orders/_lab_order.xml.builder +2 -2
  23. data/app/views/renalware/api/ukrdc/patients/lab_orders/_result_item.xml.builder +7 -2
  24. data/app/views/renalware/clinical/_header.html.slim +47 -0
  25. data/app/views/renalware/clinics/clinic_visits/_table.html.slim +0 -1
  26. data/app/views/renalware/clinics/visits/_table.html.slim +4 -1
  27. data/app/views/renalware/events/events/_event.html.slim +1 -1
  28. data/app/views/renalware/events/events/_table.html.slim +3 -0
  29. data/app/views/renalware/layouts/_patient.html.slim +1 -0
  30. data/app/views/renalware/letters/letters/_form.html.slim +1 -1
  31. data/app/views/renalware/letters/letters/_pathology.html.slim +0 -1
  32. data/app/views/renalware/mdm_patients/_table.html.slim +1 -1
  33. data/app/views/renalware/pathology/_navigation.html.slim +1 -1
  34. data/app/views/renalware/patients/patients/_table.html.slim +2 -2
  35. data/config/initializers/core_extensions.rb +1 -0
  36. data/config/initializers/inflections.rb +1 -0
  37. data/config/locales/renalware/clinical/allergies.en.yml +0 -2
  38. data/config/locales/renalware/clinical/{dry_weight.yml → dry_weight.en.yml} +0 -0
  39. data/config/locales/renalware/clinical/header.en.yml +14 -0
  40. data/db/functions/audit_view_as_json_v01.sql +25 -0
  41. data/db/functions/count_estimate_v01.sql +20 -0
  42. data/db/functions/generate_patient_secure_id_v01.sql +21 -0
  43. data/db/functions/generate_secure_id_v01.sql +18 -0
  44. data/db/functions/import_gps_csv_v01.sql +129 -0
  45. data/db/functions/import_practice_memberships_csv_v01.sql +48 -0
  46. data/db/functions/import_practices_csv_v01.sql +109 -0
  47. data/db/functions/preprocess_hl7_message_v01.sql +29 -0
  48. data/db/functions/preprocess_hl7_message_v02.sql +31 -0
  49. data/db/functions/refresh_all_matierialized_views_v01.sql +32 -0
  50. data/db/functions/refresh_current_observation_set_v01.sql +39 -0
  51. data/db/functions/update_current_observation_set_from_trigger_v01.sql +88 -0
  52. data/db/functions/update_current_observation_set_from_trigger_v02.sql +90 -0
  53. data/db/migrate/20161124152732_add_deleted_at_to_patient_bookmarks.rb +1 -1
  54. data/db/migrate/20170608135553_create_functions_to_generate_secure_patient_id.rb +4 -48
  55. data/db/migrate/20170705090219_create_refresh_all_materialized_views_fn.rb +2 -37
  56. data/db/migrate/20170707110155_rename_access_plans_to_access_plan_types.rb +1 -1
  57. data/db/migrate/20170831142819_enable_crosstab_extension.rb +2 -2
  58. data/db/migrate/20170911133224_add_type_to_messaging_messages.rb +1 -1
  59. data/db/migrate/20170915115228_add_schedule_diurnal_period_id_to_hd_profiles.rb +1 -1
  60. data/db/migrate/20171013145849_set_patients_secure_id.rb +1 -1
  61. data/db/migrate/20171101121130_create_function_to_render_audit_view_as_json.rb +2 -29
  62. data/db/migrate/20171127092158_create_function_to_import_practices.rb +4 -118
  63. data/db/migrate/20171127092359_create_fn_to_insert_gps.rb +3 -139
  64. data/db/migrate/20171206140738_create_fn_to_load_practice_memberships_csv.rb +2 -53
  65. data/db/migrate/20171213111513_create_fn_to_refresh_current_obs.rb +2 -43
  66. data/db/migrate/20171214141335_create_trigger_to_update_current_observation_sets.rb +3 -101
  67. data/db/migrate/20180119121243_create_trigger_to_preprocess_hl7_msg.rb +2 -41
  68. data/db/migrate/20180121115246_add_include_pathology_in_letter_to_letters_letterheads.rb +5 -0
  69. data/db/migrate/20180125201356_make_obs_set_trigger_change_updated_at.rb +2 -184
  70. data/db/migrate/20180130165803_add_deleted_at_indexes.rb +16 -0
  71. data/db/migrate/20180201090444_add_created_at_to_delayed_jobs_in_hl7_trig_fn.rb +9 -0
  72. data/db/migrate/20180207082540_create_count_estimate_function.rb +9 -0
  73. data/db/triggers/feed_messages_preprocessing_trigger_v01.sql +5 -0
  74. data/db/triggers/update_current_observation_set_trigger_v01.sql +7 -0
  75. data/lib/core_extensions/active_record/migration_helpers.rb +43 -0
  76. data/lib/renalware/engine.rb +6 -4
  77. data/lib/renalware/version.rb +1 -1
  78. data/spec/factories/pathology/observation_descriptions.rb +7 -1
  79. data/spec/support/shared_examples/accountable_examples.rb +6 -0
  80. data/spec/support/shared_examples/supersedable_examples.rb +12 -0
  81. metadata +33 -10
  82. data/app/models/renalware/letters/delivery/deliver_letter.rb.dead +0 -41
  83. data/app/models/renalware/pathology/current_key_observation_set.rb.dead +0 -10
  84. data/app/models/renalware/pathology/update_current_observations.rb.dead +0 -25
  85. data/app/presenters/renalware/pathology/current_observation_results.dead/presenter.rb.dead +0 -54
  86. data/app/views/renalware/letters/formatted_letters/show.rtf.slim.ol +0 -1
  87. data/app/views/renalware/patients/_prescriptions.html.slim.dead +0 -23
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d927fd26b8733bc5ec33cdc73b99b5dfffd2153a0ad5d2cad076f94594faefd1
4
- data.tar.gz: bf774fb154de01725b0322f3a149a40d691dfe81eb6d38880f72f1329ad48303
3
+ metadata.gz: 3361756d54b9a83fa22bab7f608be70508af45ecfe4b2caa6155c5a179463067
4
+ data.tar.gz: 82ae6e70d84354b6764a549e1eb8c624b2b9706b3109af3f40f739d90fdb4ca4
5
5
  SHA512:
6
- metadata.gz: b5d0501d2a554f92f4f97048b46d52800e5a0eceaed4cbf0476114f650e628de804879ed4b71fd79c7e3bea794d1b1063af800819dc173392d50f75e897c83eb
7
- data.tar.gz: 1cff8fa1d608fbdd588d63632001490c149dd2ef7d1e9d84784c6db6366996a248c86a5f23b5c00bc1c61255411a23ba3954c34560bb1716f3c5f4e9cf965eb7
6
+ metadata.gz: 2e516676b956cc6abf3189e4b270400c13282e774530051637b9c39e914c4c55d1109274b53541ec811f93f160d5aee9193dd4a3ce11943e046b46188d0e3c9b
7
+ data.tar.gz: 5c45e168decc8c91857411ff6d1736939e206030f3e75749d125798c0d12bfcfc84fe9ab04e881c4e05c74ce315bc4bb546fc37f0dc5dd57068f560a4a782fbf
@@ -31,3 +31,76 @@ article.clinical-allergies {
31
31
  }
32
32
  }
33
33
  }
34
+
35
+
36
+ .clinical-header {
37
+ background-color: $table-border-colour;
38
+ padding: .2em .2em .2em .6em;
39
+
40
+ &.lozenge {
41
+ background-color: $white;
42
+ padding: .2em .3rem;
43
+
44
+ ul {
45
+ li {
46
+ padding: 0.3rem 0.3rem;
47
+ border: none;
48
+ margin: 0.1rem 0.3rem 0.2rem 0;
49
+ border-radius: 0.3rem;
50
+ background-color: $light-grey;
51
+ }
52
+ }
53
+
54
+ dl {
55
+ padding: 0;
56
+ margin: 0;
57
+ font-size: 0.9rem;
58
+ line-height: 0.9rem;
59
+ }
60
+ }
61
+
62
+
63
+ ul {
64
+ padding: 0;
65
+ margin: 0;
66
+
67
+ li {
68
+ display: inline-block;
69
+ padding: 0;
70
+ border-bottom: 1px dotted $winter-sky;
71
+ margin: 0.2rem .6rem .2rem 0
72
+ }
73
+ }
74
+
75
+ dl {
76
+ padding: 0;
77
+ margin: 0;
78
+ font-size: 0.9rem;
79
+ line-height: 0.8rem;
80
+
81
+ dt, dd {
82
+ padding: 0;
83
+ margin: 0;
84
+ display: inline-block;
85
+ }
86
+
87
+ dt {
88
+ font-weight: normal;
89
+ }
90
+
91
+ dd {
92
+ font-weight: bold;
93
+ padding-left: 0.2rem;
94
+
95
+ &.date {
96
+ font-weight: normal;
97
+ font-style: italic;
98
+ color: $muted-dark-grey;
99
+ }
100
+
101
+ &.empty {
102
+ display: none;
103
+ }
104
+ }
105
+ }
106
+ }
@@ -0,0 +1,43 @@
1
+ require_dependency "renalware/letters"
2
+
3
+ module Renalware
4
+ module Clinics
5
+ module MostRecentMeasurementScopes
6
+ extend ActiveSupport::Concern
7
+
8
+ ClinicalObservation = Struct.new(:date, :measurement)
9
+
10
+ class_methods do
11
+ # Returns [date, weight]
12
+ def most_recent_weight_for(patient)
13
+ result = ClinicVisit
14
+ .most_recent_for_patient(patient)
15
+ .where("weight is not null")
16
+ .pluck(:date, :weight).first || []
17
+
18
+ ClinicalObservation.new(result.first, result.last)
19
+ end
20
+
21
+ # Returns [date, height]
22
+ def most_recent_height_for(patient)
23
+ result = ClinicVisit
24
+ .most_recent_for_patient(patient)
25
+ .where("height is not null")
26
+ .pluck(:date, :height).first || []
27
+
28
+ ClinicalObservation.new(result.first, result.last)
29
+ end
30
+
31
+ # Returns [date, [systolic_bp, diastolic_bp]]
32
+ def most_recent_blood_pressure_for(patient)
33
+ result = ClinicVisit
34
+ .most_recent_for_patient(patient)
35
+ .where("systolic_bp is not null and diastolic_bp is not null")
36
+ .pluck(:date, :systolic_bp, :diastolic_bp).first || [nil, nil, nil]
37
+
38
+ ClinicalObservation.new(result[0], [result[1], result[2]])
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -28,10 +28,10 @@ module Renalware
28
28
  enumerize :urine_protein, in: %i(neg trace very_low low medium high)
29
29
 
30
30
  scope :ordered, ->{ order(date: :desc, created_at: :desc) }
31
+ scope :most_recent_for_patient, ->(patient) { for_patient(patient).ordered.limit(1) }
31
32
 
32
33
  def bmi
33
- return unless weight && height && height > 0
34
- ((weight / height) / height).round(2)
34
+ BMI.new(weight: weight, height: height).to_f
35
35
  end
36
36
 
37
37
  def bp
@@ -0,0 +1,57 @@
1
+ require_dependency "renalware/letters"
2
+ require "attr_extras"
3
+
4
+ module Renalware
5
+ module Clinics
6
+ class CurrentObservations
7
+ NULL_DATE = nil
8
+ pattr_initialize :patient
9
+
10
+ Observation = Struct.new(:date, :measurement)
11
+
12
+ # Returns [date, weight]
13
+ def weight
14
+ @weight ||= begin
15
+ result = ClinicVisit
16
+ .most_recent_for_patient(patient)
17
+ .where("weight is not null")
18
+ .pluck(:date, :weight).first || []
19
+
20
+ Observation.new(result.first, result.last)
21
+ end
22
+ end
23
+
24
+ # Returns [date, height]
25
+ def height
26
+ @height ||= begin
27
+ result = ClinicVisit
28
+ .most_recent_for_patient(patient)
29
+ .where("height is not null")
30
+ .pluck(:date, :height).first || []
31
+
32
+ Observation.new(result.first, result.last)
33
+ end
34
+ end
35
+
36
+ # Returns [date, [systolic_bp, diastolic_bp]]
37
+ def blood_pressure
38
+ @blood_pressure ||= begin
39
+ result = ClinicVisit
40
+ .most_recent_for_patient(patient)
41
+ .where("systolic_bp is not null and diastolic_bp is not null")
42
+ .pluck(:date, :systolic_bp, :diastolic_bp).first || [nil, nil, nil]
43
+
44
+ Observation.new(result[0], [result[1], result[2]])
45
+ end
46
+ end
47
+
48
+ def bmi
49
+ bmi = BMI.new(
50
+ height: height.measurement,
51
+ weight: weight.measurement
52
+ )
53
+ Observation.new(NULL_DATE, bmi.to_f)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -5,6 +5,10 @@ module Renalware
5
5
  class Letter < ApplicationRecord
6
6
  include Accountable
7
7
  extend Enumerize
8
+ # The letterhead is the only site-specific element in the letter, so we use this
9
+ # to determine site-specific settings - in this case whether the letter should contain
10
+ # pathology. At KCH for example, Darren Valley letters should not contain recent pathology.
11
+ delegate :include_pathology_in_letter_body?, to: :letterhead, allow_nil: true
8
12
 
9
13
  belongs_to :event, polymorphic: true
10
14
  belongs_to :author, class_name: "User"
@@ -25,7 +29,7 @@ module Renalware
25
29
  through: :electronic_receipts,
26
30
  source: :recipient
27
31
  has_one :signature, dependent: :destroy
28
- has_one :archive, foreign_key: "letter_id"
32
+ has_one :archive, foreign_key: "letter_id", inverse_of: :letter
29
33
  serialize :pathology_snapshot, Pathology::ObservationsJsonbSerializer
30
34
 
31
35
  accepts_nested_attributes_for :main_recipient
@@ -37,5 +37,11 @@ module Renalware
37
37
  HashWithIndifferentAccess.new.extend(ObservationSetMethods)
38
38
  end
39
39
  end
40
+
41
+ class NullObservationSet
42
+ def values
43
+ ObservationsJsonbSerializer.load({})
44
+ end
45
+ end
40
46
  end
41
47
  end
@@ -12,6 +12,7 @@ module Renalware
12
12
 
13
13
  scope :ordered, -> { order(observed_at: :desc) }
14
14
  scope :for_description, ->(description) { where(description: description) }
15
+ scope :having_a_loinc_code, -> { joins(:description).where("loinc_code is not null") }
15
16
 
16
17
  def observed_on
17
18
  observed_at.to_date
@@ -17,6 +17,9 @@ module Renalware
17
17
  validates :requested_at, presence: true
18
18
 
19
19
  scope :ordered, -> { order(requested_at: :desc) }
20
+ scope :having_observations_with_a_loinc_code, lambda {
21
+ joins(observations: :description).where("loinc_code is not null") # TODO: use a merge
22
+ }
20
23
 
21
24
  def requested_on
22
25
  requested_at.to_date
@@ -5,6 +5,8 @@ module Renalware
5
5
  # We mix this module into any database-returned jsonb hash of observations
6
6
  # (e.g. CurrentObservationSet.values and Letter.pathology_snapshot)
7
7
  module ObservationSetMethods
8
+ VALID_SUFFIXES = %w(_result _observed_at).freeze
9
+
8
10
  # Support these syntaxes
9
11
  # values.hgb # => { result: ... observed_at: ...}
10
12
  # values.HGB # => { result: ... observed_at: ...}
@@ -12,15 +14,14 @@ module Renalware
12
14
  # values.hgb_observed_at # => "2017-17-01"
13
15
  # So the values has methods corresponding to the entire set of possible
14
16
  # OBX codes, and also methods to reach in and get their result and observed_at date.
15
- #
16
- # Note that if you get a missing method error for something like #hgb_result it means
17
- # that HGB does not exist yet as an ObservationDescription so is not found in
18
- # AllObservationCodes hence we can't respond to it.
19
17
  # rubocop:disable Style/MethodMissing
20
18
  def method_missing(method_name, *_args, &_block)
21
19
  code, suffix = method_parts(method_name)
22
- return super unless AllObservationCodes.include?(code)
23
- observation_hash_or_hash_element_for(code, suffix)
20
+ if VALID_SUFFIXES.include?(suffix) || AllObservationCodes.include?(code)
21
+ observation_hash_or_hash_element_for(code, suffix)
22
+ else
23
+ super
24
+ end
24
25
  end
25
26
  # rubocop:enable Style/MethodMissing
26
27
 
@@ -0,0 +1,33 @@
1
+ require_dependency "renalware"
2
+ require "attr_extras"
3
+
4
+ module Renalware
5
+ module Clinical
6
+ class HeaderPresenter
7
+ pattr_initialize :patient
8
+ delegate :weight, :height, :blood_pressure, :bmi, to: :clinical_current_observations
9
+ delegate :measurement, :date, to: :weight, prefix: true
10
+ delegate :measurement, :date, to: :height, prefix: true
11
+ delegate :measurement, :date, to: :blood_pressure, prefix: true
12
+ delegate :measurement, :date, to: :bmi, prefix: true
13
+
14
+ def current_pathology
15
+ @pathology ||= pathology_current_observation_set.values
16
+ end
17
+
18
+ private
19
+
20
+ def pathology_current_observation_set
21
+ pathology_patient.current_observation_set || Pathology::NullObservationSet.new
22
+ end
23
+
24
+ def pathology_patient
25
+ Pathology.cast_patient(patient)
26
+ end
27
+
28
+ def clinical_current_observations
29
+ @clinical_current_observations ||= Clinics::CurrentObservations.new(patient)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -41,7 +41,11 @@ module Renalware
41
41
  end
42
42
 
43
43
  def parts
44
- letter_event.part_classes.values.map do |part_class|
44
+ filtered_part_classes = PartClassFilter.new(
45
+ part_classes: letter_event.part_classes,
46
+ include_pathology_in_letter_body: letterhead.include_pathology_in_letter_body?
47
+ )
48
+ filtered_part_classes.to_h.values.map do |part_class|
45
49
  part_class.new(patient, self, letter_event)
46
50
  end
47
51
  end
@@ -0,0 +1,36 @@
1
+ require "renalware/letters"
2
+ require "attr_extras"
3
+
4
+ module Renalware
5
+ module Letters
6
+ # Given a hash of letter part classes (i.e. the class names for each Part that should be
7
+ # included in the letter, where each Part is responsible for rendering a part of the letter)
8
+ # and other options, this class filters out certain parts based on conditions,
9
+ # for example if a site does not want pathology, the recent_pathology_results key is
10
+ # removed from the hash.
11
+ class PartClassFilter
12
+ pattr_initialize [:part_classes!, :include_pathology_in_letter_body!]
13
+
14
+ def to_h
15
+ filtered_part_classes
16
+ end
17
+
18
+ private
19
+
20
+ def filtered_part_classes
21
+ remove_recent_observations_part_if_no_pathology_required_in_body(part_classes)
22
+ end
23
+
24
+ # Some sites may not require pathology in letters. This is determined by the boolean
25
+ # #include_pathology_in_letter_body flag on the letterhead, which is site-specific.
26
+ # TODO: It might be better to link the letterhead to the Hospitals::Site and
27
+ # put the site-specific configuration in say a jsonb field on the Site.
28
+ def remove_recent_observations_part_if_no_pathology_required_in_body(part_klasses)
29
+ unless include_pathology_in_letter_body
30
+ part_klasses = part_klasses.reject{ |key| key == :recent_pathology_results }
31
+ end
32
+ part_klasses
33
+ end
34
+ end
35
+ end
36
+ end
@@ -22,7 +22,6 @@ module Renalware
22
22
  def current_problems
23
23
  @current_problems ||= @patient.problems
24
24
  .current
25
- .with_patient
26
25
  .ordered
27
26
  end
28
27
 
@@ -5,7 +5,6 @@ module Renalware
5
5
  class PatientPresenter < SimpleDelegator
6
6
  delegate :allergies, to: :clinical_patient
7
7
  delegate :clinic_visits, to: :clinics_patient
8
- delegate :observation_requests, to: :pathology_patient
9
8
  delegate :profile, to: :renal_patient, allow_nil: true
10
9
  delegate :first_seen_on, to: :profile, allow_nil: true
11
10
  alias_attribute :home_telephone, :telephone1
@@ -17,7 +16,7 @@ module Renalware
17
16
 
18
17
  def current_modality_hd?
19
18
  return false if current_modality.blank?
20
- current_modality.description.is_a?(HD::ModalityDescription)
19
+ current_modality.description.is_a?(Renalware::HD::ModalityDescription)
21
20
  end
22
21
 
23
22
  def smoking_history
@@ -46,6 +45,13 @@ module Renalware
46
45
  email || home_telephone || mobile_telephone
47
46
  end
48
47
 
48
+ def observation_requests
49
+ pathology_patient
50
+ .observation_requests
51
+ .having_observations_with_a_loinc_code
52
+ .where(patient_id: id)
53
+ end
54
+
49
55
  private
50
56
 
51
57
  def clinical_patient
@@ -0,0 +1,20 @@
1
+ require "attr_extras"
2
+
3
+ module Renalware
4
+ # Value object representing Body Mass Index. Accepts only meters and kg
5
+ #
6
+ # Example usage:
7
+ # bmi = Renalware::BMI.new(height: 1.80, weight: 180)
8
+ # bmi.to_f # => 55.6
9
+ # bmi.to_s # => "55.6"
10
+ #
11
+ class BMI
12
+ pattr_initialize [:weight!, :height!]
13
+ delegate :to_s, to: :to_f
14
+
15
+ def to_f
16
+ return unless weight && height && height > 0
17
+ ((weight / height) / height).round(1)
18
+ end
19
+ end
20
+ end
@@ -6,7 +6,7 @@
6
6
  th.at-least.col-width-medium Patient
7
7
  th.col-width-nhs-no NHS No.
8
8
  th.col-width-reference-no Hosp Nos.
9
- th.col-width-small Modality
9
+ th.col-width-medium Modality
10
10
  th.col-width-tiny Sex
11
11
  th.col-width-tiny Age
12
12
  th.col-width-small AKI Risk