Skip to content

Drink driving penalty calculator

A four-question interactive tool on motoring-offence service pages that estimates the likely disqualification and penalty for a drink-driving charge, gated behind a Contact Form 7 form so the visitor’s details are captured before the result is shown.

It is a lead-generation tool that outputs sentencing estimates, so the content of its lookup table is legal advice as much as it is code.

Someone facing a drink-driving charge searches for “how long will I be banned”. The tool answers that question in exchange for contact details — the result panel is only reachable after the CF7 form step — and the answers double as qualification data for the road-traffic team (test type, reading band, prior convictions, circumstances).

Path What it does
wp-content/themes/bpcollins/blocks/drink-drive-calculator/block-drink-drive-calculator.php Question definitions and the whole markup, including the gate form and result panel
wp-content/themes/bpcollins/blocks/drink-drive-calculator/block-drink-drive-calculator.js Step navigation, hidden-field population and the penalty lookup
wp-content/themes/bpcollins/single-service.php:19-21 Adds the block’s assets when show_drink_driving_section is set
wp-content/themes/bpcollins/single-service.php:~786 requires the block template on such pages

Questions are a PHP array in the template (block-drink-drive-calculator.php:5-150), each entry holding a title, a control class, a name, a prev (the step to go back to) and options carrying value plus a goto naming the next step. The template renders one <form> per question with radio inputs; navigation is driven entirely by those data-goto / data-prev attributes.

The flow is:

  1. Type — breath, blood or urine. Choosing one sets which reading question comes next, and updateType() rewrites the convictions panel’s data-prev so the back button returns to the right reading step.
  2. Reading — four bands, different per test type (breath 36-59 … 120-150+; blood 81-137 … 276-345+; urine 108-183 … 367-459+).
  3. Previous convictions — yes/no.
  4. Circumstances — stopped due to manner of driving or an accident, yes/no.
  5. The gate — the dd-form panel renders a CF7 form by id from the block’s drink_drive_form_id field. populateHiddenFields() writes the four answers into hidden CF7 inputs named type, <type> (i.e. breath/blood/urine), previous-conviction and stopped-by-police, so the enquiry email carries the full answer set.
  6. The resultcalculateResults() matches the reading against four equivalence groups (each group listing the equivalent breath/blood/urine band), then convictionsAndStoppedResult() picks one of four strings by the convictions × stopped combination. Sixteen outcomes total, ranging from “12 to 14 month disqualification and fine of up to £5000” to “58 to 60 months disqualification and up to 26 weeks in prison”.

A shared “Next” button is retargeted at each step by setSubmitButtonTarget(), which rewrites its form attribute — that is how one button submits whichever panel is active. The result panel carries two fixed disclaimers stating the result is a guide only, plus an optional CTA from the dd_get_in_touch field.

flowchart TD
A[cover: Start the test] --> B[type: breath/blood/urine]
B --> C[reading band for that type]
C --> D[previous convictions y/n]
D --> E[stopped by police y/n]
E --> F[dd-form: CF7 form, answers written to hidden fields]
F --> G[dd-results: lookup by reading group × convictions × stopped]
G --> H[disclaimers + optional CTA]
  • Per service post: show_drink_driving_section (boolean) — gates both the asset enqueue and the render.
  • Per block: drink_drive_form_id (the CF7 form id used as the gate), dd_get_in_touch (a link field for the result CTA).
  • The CF7 form must contain hidden fields named exactly type, breath, blood, urine, previous-conviction and stopped-by-police.
  • The penalty table itself is not configurable — all 16 outcome strings and all reading bands are hardcoded in the JS (block-drink-drive-calculator.js:234-275).
  • Sentencing content lives in JavaScript. Updating guideline ranges means editing block-drink-drive-calculator.js and rebuilding with gulp. An editor cannot change these numbers. Anyone asked to “update the penalties” needs a developer.
  • Reading bands are string-matched. matchResults() does results.includes(this[this.type]), comparing the selected option’s literal value against a hardcoded list (block-drink-drive-calculator.js:296-299). Changing a label like 36-59 in the PHP question array without changing the matching string in the JS makes the result silently blank — no error, an empty result panel.
  • calculateResults() has no else branch. If nothing matches, the result title keeps whatever was there before (empty on first run).
  • The result is computed client-side and the answers are in the DOM. The gate is a UX gate, not a security boundary — the outcome strings are readable in the built JS without submitting anything.
  • populateHiddenFields() assumes every hidden field exists and dereferences the query result directly (block-drink-drive-calculator.js:191-201). If the CF7 form is edited and a hidden field is renamed or dropped, the step throws and the visitor cannot progress past the form.
  • Panels are addressed by document-level ids (type, breath, blood, urine, convictions, stopped, dd-form, dd-results, dd-calculator). Two calculators on one page would collide.
  • The block is only wired into service pages. It registers as an ACF block like any other, but the show_drink_driving_section field and the require are service-specific, so placing it via the editor elsewhere has not been exercised.
  • Disclaimers are hardcoded in the template — they are part of the compliance posture of the tool, not decoration. Do not remove them.
  • Updating guideline ranges → edit the four arrays in calculateResults() and, if bands change, the option values in the PHP question array and the matching strings in matchResults() groups. Then rebuild with gulp. Get the new figures signed off by the road-traffic team; this is published legal guidance.
  • Adding a question → add an entry to the $questions array with correct prev and goto wiring, add a progress <li> with a matching data-page, and extend the result logic. Navigation is entirely attribute-driven, so a wrong goto produces a dead end rather than an error.
  • Changing the gate form → keep the six hidden field names, or update populateHiddenFields() in the same commit.
  • Deliberately not abstracted: the penalty table is inline in the JS rather than in ACF. That was presumably a speed decision; moving it into fields would be a real improvement if the guidelines change often.
  • Verify: walk all three test types, use the back button at every step, submit the gate form, and confirm the enquiry email contains all four answers and that the displayed result matches the table for that combination.

None. No automated tests exist in this repository — and the 16-outcome lookup is exactly the kind of logic that would benefit from them. Verification is a manual walk of the paths that matter.