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.
Why it exists
Section titled “Why it exists”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).
Entry points
Section titled “Entry points”| 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 |
How it works
Section titled “How it works”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:
- Type — breath, blood or urine. Choosing one sets which reading question
comes next, and
updateType()rewrites theconvictionspanel’sdata-prevso the back button returns to the right reading step. - Reading — four bands, different per test type (breath 36-59 … 120-150+; blood 81-137 … 276-345+; urine 108-183 … 367-459+).
- Previous convictions — yes/no.
- Circumstances — stopped due to manner of driving or an accident, yes/no.
- The gate — the
dd-formpanel renders a CF7 form by id from the block’sdrink_drive_form_idfield.populateHiddenFields()writes the four answers into hidden CF7 inputs namedtype,<type>(i.e.breath/blood/urine),previous-convictionandstopped-by-police, so the enquiry email carries the full answer set. - The result —
calculateResults()matches the reading against four equivalence groups (each group listing the equivalent breath/blood/urine band), thenconvictionsAndStoppedResult()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]Configuration
Section titled “Configuration”- 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-convictionandstopped-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).
Invariants and gotchas
Section titled “Invariants and gotchas”- Sentencing content lives in JavaScript. Updating guideline ranges means
editing
block-drink-drive-calculator.jsand rebuilding with gulp. An editor cannot change these numbers. Anyone asked to “update the penalties” needs a developer. - Reading bands are string-matched.
matchResults()doesresults.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 like36-59in 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_sectionfield and therequireare 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.
Changing it safely
Section titled “Changing it safely”- 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 inmatchResults()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
$questionsarray with correctprevandgotowiring, add a progress<li>with a matchingdata-page, and extend the result logic. Navigation is entirely attribute-driven, so a wronggotoproduces 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.