Skip to content

Commit b5bf568

Browse files
authored
Cache consent status in database (#3263)
This introduces a new model, `Patient::ConsentStatus`, which stores the status of a patient-programme pair in the database so it can queried and rendered quickly. Locally, I'm seeing the consent tab on sessions with 50,000 patients load almost instantly even when filtering on statuses. The session overview page has a slight improvement, but that will only load quickly once the other statuses are cached. I've decided not to implement a state machine as had been previously discussed, but this could be built on top of this change to enhancement the functionality. For now, this effectively caches the existing logic that runs on the fly to the database. I've also decided not to implement this using Rails callbacks and instead be explicit where the status is refreshed. This allows us to optimise for bulk refreshes, as is necessary when adding new programmes to sessions, running the seeds, or generally updating the status of an entire session. This is proposed as an alternative to #3262 for consent statuses, we'd need to apply the same functionality to triage, programmes, sessions and next actions. If we're happy with this approach, we can migrate the statuses one by one to avoid a big bang approach unlike #3262.
2 parents 41a5645 + c497667 commit b5bf568

File tree

73 files changed

+899
-434
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+899
-434
lines changed

app/components/app_consent_component.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<%= render AppConsentStatusComponent.new(patient_session:, programme:) %>
22

3-
<% if patient.consent_outcome.no_response?(programme) %>
3+
<% if consent_status.no_response? %>
44
<% if latest_consent_request %>
55
<p class="nhsuk-body">
66
No-one responded to our requests for consent.

app/components/app_consent_component.rb

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ def initialize(patient_session:, programme:)
1414

1515
def consents
1616
@consents ||=
17-
patient.consent_outcome.all[programme].sort_by(&:created_at).reverse
17+
patient
18+
.consents
19+
.where(programme:)
20+
.includes(:consent_form, :parent)
21+
.order(created_at: :desc)
1822
end
1923

2024
def latest_consent_request
@@ -27,10 +31,13 @@ def latest_consent_request
2731
.first
2832
end
2933

34+
def consent_status
35+
@consent_status ||= patient.consent_status(programme:)
36+
end
37+
3038
def can_send_consent_request?
31-
patient.consent_outcome.no_response?(programme) &&
32-
patient.send_notifications? && session.open_for_consent? &&
33-
patient.parents.any?
39+
consent_status.no_response? && patient.send_notifications? &&
40+
session.open_for_consent? && patient.parents.any?
3441
end
3542

3643
def status_colour(consent)

app/components/app_consent_status_component.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,12 @@ def initialize(patient_session:, programme:)
99
end
1010

1111
def call
12-
case patient.consent_outcome.status[programme]
13-
when Patient::ConsentOutcome::GIVEN
12+
consent_status = patient.consent_status(programme:)
13+
if consent_status.given?
1414
icon_tick "Consent given", "aqua-green"
15-
when Patient::ConsentOutcome::REFUSED
15+
elsif consent_status.refused?
1616
icon_cross "Consent refused", "red"
17-
when Patient::ConsentOutcome::CONFLICTS
17+
elsif consent_status.conflicts?
1818
icon_cross "Conflicting consent", "dark-orange"
1919
end
2020
end

app/components/app_patient_page_component.html.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@
6767

6868
<% if display_health_questions? %>
6969
<%= render AppHealthAnswersCardComponent.new(
70-
patient.consent_outcome.latest[programme],
70+
patient.latest_consents(programme:),
7171
heading: "All answers to health questions",
7272
) %>
7373
<% end %>

app/components/app_patient_page_component.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ def initialize(
2424
delegate :patient, :session, to: :patient_session
2525

2626
def display_health_questions?
27-
patient.consent_outcome.latest[programme].any?(&:response_given?)
27+
patient.latest_consents(programme:).any?(&:response_given?)
2828
end
2929

3030
def display_gillick_assessment_card?

app/components/app_patient_session_search_result_card_component.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,15 @@ def status_tag
8787
render AppRegisterStatusTagComponent.new(
8888
patient_session.register_outcome.status
8989
)
90+
elsif context == :consent
91+
statuses =
92+
patient_session.programmes.index_with do |programme|
93+
patient.consent_status(programme:).status
94+
end
95+
render AppProgrammeStatusTagsComponent.new(statuses, outcome: :consent)
9096
else
9197
outcome =
9298
case context
93-
when :consent
94-
patient.consent_outcome
9599
when :triage
96100
patient.triage_outcome
97101
when :outcome

app/components/app_programme_session_table_component.rb

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,30 +13,33 @@ def initialize(sessions, programme:)
1313
attr_reader :sessions, :programme
1414

1515
def cohort_count(session:)
16-
session.patient_sessions.length.to_s
16+
format_number(session.patient_sessions.count)
1717
end
1818

1919
def number_stat(session:)
20-
session.patient_sessions.select { yield it }.length.to_s
20+
format_number(session.patient_sessions.select { yield it }.length)
2121
end
2222

2323
def percentage_stat(session:)
24-
total_count = session.patient_sessions.length
25-
return nil if total_count.zero?
26-
27-
count = session.patient_sessions.select { yield it }.length
24+
format_percentage(
25+
session.patient_sessions.select { yield it }.length,
26+
session.patient_sessions.count
27+
)
28+
end
2829

29-
number_to_percentage(count / total_count.to_f * 100.0, precision: 0)
30+
def no_response_scope(session:)
31+
session.patient_sessions.has_consent_status(:no_response, programme:)
3032
end
3133

3234
def no_response_count(session:)
33-
number_stat(session:) { it.patient.consent_outcome.no_response?(programme) }
35+
format_number(no_response_scope(session:).count)
3436
end
3537

3638
def no_response_percentage(session:)
37-
percentage_stat(session:) do
38-
it.patient.consent_outcome.no_response?(programme)
39-
end
39+
format_percentage(
40+
no_response_scope(session:).count,
41+
session.patient_sessions.count
42+
)
4043
end
4144

4245
def triage_needed_count(session:)
@@ -50,4 +53,12 @@ def vaccinated_count(session:)
5053
def vaccinated_percentage(session:)
5154
percentage_stat(session:) { it.session_outcome.vaccinated?(programme) }
5255
end
56+
57+
def format_number(count) = count.to_s
58+
59+
def format_percentage(count, total_count)
60+
return nil if total_count.zero?
61+
62+
number_to_percentage(count / total_count.to_f * 100.0, precision: 0)
63+
end
5364
end

app/components/app_session_actions_component.rb

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ def render?
2121

2222
attr_reader :session, :patient_sessions
2323

24+
delegate :programmes, to: :session
25+
2426
def rows
2527
@rows ||= [
2628
no_consent_response_row,
@@ -32,24 +34,16 @@ def rows
3234
end
3335

3436
def no_consent_response_row
35-
consent_row(
36-
text: "No consent response",
37-
status: Patient::ConsentOutcome::NO_RESPONSE
38-
)
37+
consent_row("No consent response", status: "no_response")
3938
end
4039

4140
def conflicting_consent_row
42-
consent_row(
43-
text: "Conflicting consent",
44-
status: Patient::ConsentOutcome::CONFLICTS
45-
)
41+
consent_row("Conflicting consent", status: "conflicts")
4642
end
4743

48-
def consent_row(text:, status:)
44+
def consent_row(text, status:)
4945
count =
50-
patient_sessions.count do
51-
it.patient.consent_outcome.status.values_at(*it.programmes).any?(status)
52-
end
46+
patient_sessions.has_consent_status(status, programme: programmes).count
5347

5448
return nil if count.zero?
5549

app/components/app_session_details_summary_component.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ def call
1616

1717
attr_reader :session, :patient_sessions
1818

19+
delegate :programmes, to: :session
20+
1921
def cohort_row
2022
count = patient_sessions.length
2123
href = new_draft_class_import_path(session)
@@ -32,11 +34,11 @@ def cohort_row
3234
end
3335

3436
def consent_refused_row
35-
status = Patient::ConsentOutcome::REFUSED
37+
status = "refused"
38+
3639
count =
37-
patient_sessions.count do
38-
it.patient.consent_outcome.status.values_at(*it.programmes).any?(status)
39-
end
40+
patient_sessions.has_consent_status(status, programme: programmes).count
41+
4042
href =
4143
session_consent_path(session, search_form: { consent_status: status })
4244

app/components/app_simple_status_banner_component.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def description
5252

5353
if patient.triage_outcome.required?(programme)
5454
reasons = [
55-
if patient.triage_outcome.consent_needs_triage?(programme:)
55+
if patient.consent_status(programme:).health_answers_require_follow_up?
5656
I18n.t(
5757
:consent_needs_triage,
5858
scope: %i[
@@ -83,10 +83,11 @@ def description
8383
end
8484

8585
def who_refused
86-
patient.consent_outcome.latest[programme]
86+
patient
87+
.latest_consents(programme:)
8788
.select(&:response_refused?)
88-
.map(&:who_responded)
8989
.last
90+
&.who_responded
9091
end
9192

9293
def nurse

0 commit comments

Comments
 (0)