Skip to content

Commit dd2f253

Browse files
Merge pull request #6693 from NHSDigital/consent-breakdown-v2
Add consent refusal reason and route breakdowns
2 parents f2616a2 + 7e5d11d commit dd2f253

2 files changed

Lines changed: 286 additions & 0 deletions

File tree

app/controllers/api/reporting/totals_controller.rb

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,11 +148,53 @@ def render_totals_json
148148
consent_no_response: metrics.consent_no_response,
149149
consent_refused: metrics.consent_refused,
150150
consent_conflicts: metrics.consent_conflicts,
151+
consent_refusal_reasons: team_consent_refusal_reasons,
152+
consent_routes: team_consent_routes,
151153
vaccinations_given: team_vaccinations_given_count,
152154
monthly_vaccinations_given: team_monthly_vaccinations_given
153155
}
154156
end
155157

158+
def team_consent_refusal_reasons
159+
counts =
160+
latest_scoped_consents
161+
.where(response: :refused)
162+
.where.not(reason_for_refusal: nil)
163+
.group(:reason_for_refusal)
164+
.count
165+
166+
Consent.reason_for_refusals.keys.index_with { |key| counts[key] || 0 }
167+
end
168+
169+
def team_consent_routes
170+
counts = latest_scoped_consents.group(:route).count
171+
Consent.routes.keys.index_with { |key| counts[key] || 0 }
172+
end
173+
174+
def latest_scoped_consents
175+
base =
176+
Consent
177+
.where(patient_id: @totals_scope.select(:patient_id))
178+
.where(team_id: @team&.id || current_user.team_ids)
179+
.where(academic_year: params[:academic_year])
180+
.not_invalidated
181+
.not_withdrawn
182+
.response_provided
183+
184+
if params[:programme].present?
185+
base = base.where(programme_type: params[:programme])
186+
end
187+
188+
Consent.where(
189+
id:
190+
base.select(
191+
"DISTINCT ON (patient_id, programme_type, parent_id) id"
192+
).order(
193+
Arel.sql("patient_id, programme_type, parent_id, submitted_at DESC")
194+
)
195+
)
196+
end
197+
156198
def apply_workgroup_filter
157199
workgroup = params[:workgroup].presence || cis2_info.team_workgroup
158200
return unless workgroup

spec/controllers/api/reporting/totals_controller_spec.rb

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
expect(parsed_response).to have_key("not_vaccinated")
2020
expect(parsed_response).to have_key("vaccinations_given")
2121
expect(parsed_response).to have_key("monthly_vaccinations_given")
22+
expect(parsed_response).to have_key("consent_refusal_reasons")
23+
expect(parsed_response).to have_key("consent_routes")
2224
end
2325

2426
it "calculates statistics correctly" do
@@ -357,6 +359,248 @@
357359
expect(parsed_response["consent_conflicts"]).to eq(1)
358360
expect(parsed_response["consent_no_response"]).to eq(4)
359361
end
362+
363+
describe "consent breakdowns" do
364+
let(:team) { Team.last }
365+
let(:programme) { Programme.hpv }
366+
let(:session) { create(:session, team:, programmes: [programme]) }
367+
368+
before { team.programmes << programme }
369+
370+
it "returns the breakdown shape with zero counts when no consents exist" do
371+
create(:patient, session:)
372+
373+
refresh_reporting_views!
374+
375+
get :index, params: { programme: "hpv" }
376+
377+
expect(parsed_response["consent_refusal_reasons"]).to eq(
378+
"contains_gelatine" => 0,
379+
"already_vaccinated" => 0,
380+
"will_be_vaccinated_elsewhere" => 0,
381+
"medical_reasons" => 0,
382+
"personal_choice" => 0,
383+
"other" => 0,
384+
"do_not_want_vaccination_at_school" => 0
385+
)
386+
expect(parsed_response["consent_routes"]).to eq(
387+
"website" => 0,
388+
"phone" => 0,
389+
"paper" => 0,
390+
"in_person" => 0,
391+
"self_consent" => 0
392+
)
393+
end
394+
395+
it "counts each parent's refusal reason when two parents refuse the same patient" do
396+
patient = create(:patient, session:)
397+
parent_one = create(:parent)
398+
parent_two = create(:parent)
399+
create(:parent_relationship, patient:, parent: parent_one)
400+
create(:parent_relationship, patient:, parent: parent_two)
401+
create(
402+
:consent,
403+
:refused,
404+
patient:,
405+
programme:,
406+
team:,
407+
parent: parent_one,
408+
reason_for_refusal: "medical_reasons"
409+
)
410+
create(
411+
:consent,
412+
:refused,
413+
patient:,
414+
programme:,
415+
team:,
416+
parent: parent_two,
417+
reason_for_refusal: "personal_choice"
418+
)
419+
PatientStatusUpdater.call(patient:)
420+
421+
refresh_reporting_views!
422+
423+
get :index, params: { programme: "hpv" }
424+
425+
reasons = parsed_response["consent_refusal_reasons"]
426+
expect(reasons["medical_reasons"]).to eq(1)
427+
expect(reasons["personal_choice"]).to eq(1)
428+
end
429+
430+
it "counts each parent's route when two parents respond via different routes" do
431+
patient = create(:patient, session:)
432+
parent_one = create(:parent)
433+
parent_two = create(:parent)
434+
recorded_by = create(:user)
435+
create(:parent_relationship, patient:, parent: parent_one)
436+
create(:parent_relationship, patient:, parent: parent_two)
437+
create(
438+
:consent,
439+
:given,
440+
patient:,
441+
programme:,
442+
team:,
443+
parent: parent_one,
444+
route: "website"
445+
)
446+
create(
447+
:consent,
448+
:given,
449+
patient:,
450+
programme:,
451+
team:,
452+
parent: parent_two,
453+
route: "phone",
454+
recorded_by:
455+
)
456+
PatientStatusUpdater.call(patient:)
457+
458+
refresh_reporting_views!
459+
460+
get :index, params: { programme: "hpv" }
461+
462+
routes = parsed_response["consent_routes"]
463+
expect(routes["website"]).to eq(1)
464+
expect(routes["phone"]).to eq(1)
465+
end
466+
467+
it "counts only the latest consent when one parent updates their refusal reason" do
468+
patient = create(:patient, session:)
469+
parent = create(:parent)
470+
create(:parent_relationship, patient:, parent:)
471+
create(
472+
:consent,
473+
:refused,
474+
patient:,
475+
programme:,
476+
team:,
477+
parent:,
478+
reason_for_refusal: "medical_reasons",
479+
submitted_at: 2.days.ago
480+
)
481+
create(
482+
:consent,
483+
:refused,
484+
patient:,
485+
programme:,
486+
team:,
487+
parent:,
488+
reason_for_refusal: "personal_choice",
489+
submitted_at: 1.day.ago
490+
)
491+
PatientStatusUpdater.call(patient:)
492+
493+
refresh_reporting_views!
494+
495+
get :index, params: { programme: "hpv" }
496+
497+
reasons = parsed_response["consent_refusal_reasons"]
498+
expect(reasons["medical_reasons"]).to eq(0)
499+
expect(reasons["personal_choice"]).to eq(1)
500+
end
501+
502+
it "counts the refusal reason for a patient in conflicts status" do
503+
patient = create(:patient, session:)
504+
parent_giving = create(:parent)
505+
parent_refusing = create(:parent)
506+
create(:parent_relationship, patient:, parent: parent_giving)
507+
create(:parent_relationship, patient:, parent: parent_refusing)
508+
create(
509+
:consent,
510+
:given,
511+
patient:,
512+
programme:,
513+
team:,
514+
parent: parent_giving,
515+
route: "website"
516+
)
517+
create(
518+
:consent,
519+
:refused,
520+
patient:,
521+
programme:,
522+
team:,
523+
parent: parent_refusing,
524+
reason_for_refusal: "medical_reasons",
525+
route: "phone",
526+
recorded_by: create(:user)
527+
)
528+
PatientStatusUpdater.call(patient:)
529+
530+
refresh_reporting_views!
531+
532+
get :index, params: { programme: "hpv" }
533+
534+
expect(parsed_response["consent_conflicts"]).to eq(1)
535+
expect(
536+
parsed_response["consent_refusal_reasons"]["medical_reasons"]
537+
).to eq(1)
538+
expect(parsed_response["consent_routes"]["website"]).to eq(1)
539+
expect(parsed_response["consent_routes"]["phone"]).to eq(1)
540+
end
541+
542+
it "counts both self-consent and parental consent on the same patient" do
543+
patient = create(:patient, session:, year_group: 11)
544+
parent = create(:parent)
545+
create(:parent_relationship, patient:, parent:)
546+
create(
547+
:consent,
548+
:given,
549+
patient:,
550+
programme:,
551+
team:,
552+
parent:,
553+
route: "website"
554+
)
555+
create(:consent, :given, :self_consent, patient:, programme:, team:)
556+
PatientStatusUpdater.call(patient:)
557+
558+
refresh_reporting_views!
559+
560+
get :index, params: { programme: "hpv" }
561+
562+
routes = parsed_response["consent_routes"]
563+
expect(routes["website"]).to eq(1)
564+
expect(routes["self_consent"]).to eq(1)
565+
end
566+
567+
it "excludes invalidated and withdrawn consents" do
568+
patient = create(:patient, session:)
569+
parent_one = create(:parent)
570+
parent_two = create(:parent)
571+
create(:parent_relationship, patient:, parent: parent_one)
572+
create(:parent_relationship, patient:, parent: parent_two)
573+
create(
574+
:consent,
575+
:refused,
576+
:invalidated,
577+
patient:,
578+
programme:,
579+
team:,
580+
parent: parent_one,
581+
reason_for_refusal: "medical_reasons"
582+
)
583+
create(
584+
:consent,
585+
:refused,
586+
:withdrawn,
587+
patient:,
588+
programme:,
589+
team:,
590+
parent: parent_two,
591+
reason_for_refusal: "personal_choice"
592+
)
593+
PatientStatusUpdater.call(patient:)
594+
595+
refresh_reporting_views!
596+
597+
get :index, params: { programme: "hpv" }
598+
599+
reasons = parsed_response["consent_refusal_reasons"]
600+
expect(reasons["medical_reasons"]).to eq(0)
601+
expect(reasons["personal_choice"]).to eq(0)
602+
end
603+
end
360604
end
361605

362606
describe "#index.csv" do

0 commit comments

Comments
 (0)