A mileage claim checker on AWS for a few dollars a month
Mileage is the expense line nobody wants to police. The amounts are small, the claims are frequent, and checking one properly means opening a map, typing two addresses, and comparing a number to a number — for four dollars. So nobody checks them, and the line quietly grows, and the one person who is rounding up never finds out that anyone noticed. This post walks through a small system that does the boring comparison on every claim, pays the ordinary ones without anyone looking, and turns the odd ones into a single neutral question.

Key takeaways
- One way to claim: a phone form with the date, two addresses and a reason. Three taps.
- Every claim ends in one of three states: paid, asked about, or waiting on a person.
- The expected distance comes from a routing lookup, and it is a reference, not a rule.
- The tolerance band is yours, in a sheet. Wide by default, because real driving is untidy.
- Designed on AWS for about $4 a month at typical small-business volume.
The whole system on one page
Before any code, here is the shape of what we are designing.
- App integration
- Analytics
- Front-end & mobile
- People
What you set up once (the outside)
- A claim form. Three taps on a phone: the date, where from, where to, and a one-line reason. Covered in Part 2. No app to install — a web form on a saved home-screen link, because the alternative is a photo of a handwritten sheet at month end.
- A routing lookup. Any provider that turns two addresses into a driving distance. This is the only paid third party in the design, it is called at most once per claim, and the answer is cached forever against that address pair, because the distance between two fixed places does not change.
- A tolerance sheet. One tab: the band around the expected distance that counts as ordinary, the monthly total above which a person always looks, and the per-mile rate. The band is deliberately wide — a default of plus fifty per cent — because real trips include getting lost, roadworks, and dropping something off on the way.
What runs on every claim (the inside)
- The intake. Cleans the two addresses into something a routing service will accept, which is most of the work. “the Henderson site” is not an address; the intake matches it against the customer list first, and only asks the claimant if it genuinely cannot resolve it. It also assigns the claim a fingerprint so a resubmission cannot become a second payment.
- The checker. Three comparisons, none of them clever. Is the claimed distance inside the band around the expected one? Has this same trip already been claimed this month? Is this claimant’s month unusual against their own history — not against anybody else’s? The third is the one that catches real problems, and it is also the one most likely to be wrong, so it never blocks a payment on its own.
- The router. Inside the band and not a repeat: pay it, log it, tell nobody. Outside the band: reply to the claimant with the two numbers and one question. Repeat, or a month well outside their pattern: send it to a person with both claims side by side. Nothing is rejected by the system, ever.
One claim, end to end
- App integration
- Analytics
- Front-end & mobile
In plain words
Your engineer finishes at a customer in Ashford and drives to one in Maidstone. That evening he opens the saved link on his phone, taps today’s date, picks both sites from the recent list, types “call-out, boiler”, and submits. The intake resolves both sites to their postcodes from the customer list. The routing lookup says 27 miles. He claimed 29. The band is plus or minus fifty per cent, so 29 is comfortably ordinary. It is paid, added to his month, and nobody is told. Elapsed time: about eleven seconds, and he is the only person who spent any of it.
The following week he claims 84 miles for a trip the routing service thinks is 41. That is outside the band, so instead of paying or refusing, the system replies: “This route looks like about 41 miles and you claimed 84 — was this a return trip?” He taps yes. It is paid. The entire exchange took him four seconds and it never reached you. That is the difference between a checker and an audit: the checker asks the question the auditor would have asked, immediately, of the one person who already knows the answer.
Design rules that shaped every decision
- It asks, it does not accuse. Every out-of-band claim gets a neutral question with both numbers in it, and a one-tap answer that closes it.
- The expected distance is a reference, not a rule. Real driving is untidy and the band is wide by default.
- Compare a person against their own history, never against a colleague’s. Rounds are different, patches are different, lives are different.
- One trip, one payment. A fingerprint on claimant, date and both endpoints makes a resubmission land on the original claim.
- The routing answer is cached forever. Two fixed addresses do not move, and paying twice for the same lookup is just waste.
- Nothing is ever rejected by the system. The worst outcome is a person looking at it.
Why this shape
The two usual approaches both fail in the same way. Either nobody checks, and the line grows until somebody notices at year end and then everybody gets a stern email including the twelve people who were scrupulous. Or somebody checks all of them, which costs more in their time than the entire mileage budget and makes every claimant feel suspected.
The shape above does the arithmetic on every single claim, which no human will, and then does almost nothing with the result. Ninety per cent of claims are paid silently. Most of the rest are closed by the claimant in one tap. What reaches you is the handful that are genuinely worth two minutes — and it reaches you with the map already drawn and both numbers on the screen.
The next four posts walk through each piece: how a claim arrives, how the distance gets checked, how a question reaches the claimant, and how a payment gets recorded. One diagram per post, a cost breakdown, and an engineering reference at the end.
All posts