The problem
UK parking penalties escalate on a timer, and the escalation is the harm. Councils, TfL and private operators all run different rules; miss a date and the fine rises or the right to appeal disappears entirely. Then there is the paperwork — PE2, PE3, TE7, TE9, Order for Recovery, Charge Certificate — complicated enough that most people just pay.
What it does
You photograph the ticket and it extracts the reference, registration, issuer, amount and dates, then tracks the 14-day discount and 28-day payment windows with reminders and a status timeline that advances as deadlines pass. If the ticket looks challengeable it scores the odds from historical tribunal outcomes, drafts a representation grounded in the relevant statutory provisions, fills the PDFs, and can submit to the issuer’s own portal once you approve it.
How it's built
Issuer automation that repairs itself by opening a PR
Hand-written Playwright adapters run locally and free. When a council redesigns its portal and one breaks, the request falls back to a Stagehand agent on a Browserbase cloud browser, which learns the flow and opens a pull request adding a typed adapter. Repairs must pass selector contracts and a live canary before merge, a review model reads the diff, and blast radius is capped at one repair per run.
Rewriting a scraper by deleting the browser
A Playwright scraper of the London Tribunals register managed about 4% of it in a year. The register is a server-rendered APEX app with a date-range search, so I rewrote it as a plain HTTP client — no browser, no xvfb, no day-by-day traversal. Per-day coverage lives in Postgres as expected-versus-covered counts, so a backfill is restartable and "done" is provable rather than assumed.
Confidence-weighted OCR consensus, on device
Across a framing session each OCR pass votes per field, weighted by Vision’s own line confidence, so a sharp read outweighs a blurry one. The reference extractor encodes real domain knowledge: on a bailiff letter "Client Ref" is the council’s PCN and is accepted, while "Our Ref" and the agent’s own case ID are excluded.
Picking the extraction model by measurement, not by feel
The appeal-pattern extractor was chosen with a bake-off: 200 cases graded against a gold reference, five models scored on jaccard, precision and recall. The winner was statistically indistinguishable from the best performer at roughly 40% of the cost. It runs through the Batch API and is stamped incrementally so it never double-counts.
Street View as evidence for a signage appeal
Inadequate signage is one of the strongest appeal grounds, so a collector takes the ticket’s coordinates, checks Street View coverage, pulls imagery at four headings and runs a vision model over it to judge whether restriction signage is actually visible. It is one of eight pluggable collectors run concurrently, feeding both the prediction and the letter.
The hard part
Two things. Keeping browser automation alive against sites that change without notice and actively resist bots — solved with tiers (free local Playwright when healthy, a cloud browser with managed CAPTCHA solving where needed, an AI agent only for genuine drift) rather than paying for the expensive path on every request. And measuring detection honestly when the tooling lies: Apple’s document segmentation is broken on iOS simulators in a way that looks like success under CPU-forcing, returning a byte-identical quad for every input. So detection quality is validated by a Swift harness running real Vision requests natively on macOS over a corpus of real photos, and the simulator is only ever trusted for plumbing.
Stack
- Next.js
- React Native
- Expo
- Prisma
- Postgres
- Anthropic
- OpenAI
- Google Vision OCR
- Stripe
- RevenueCat
Built, shipped and maintained by me. Try Parking Ticket Pal, or see the rest of the work.
