Skip to content

Commit 7e5d11d

Browse files
Add consent refusal reason and route breakdowns
Teams can now see why parents refused consent (personal choice, gelatine concern, etc.) and how consent was submitted (website, phone, paper).
1 parent f2616a2 commit 7e5d11d

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)