Inspiration

I work at a logistics company in Nigeria. Our drivers don't fill in forms — they send WhatsApp voice notes, or nothing, and the office pieces the day together by phone. Odometer readings and fuel purchases are never cross-checked, so fuel leakage and route deviations go unnoticed for weeks. Fleet software assumes GPS hardware, strong connectivity, and drivers who type. None of that describes a haulage route from Kaduna to Kano.

The input drivers already produce — a 30-second voice note — is enough. The AI can do the structuring. That was the whole idea.

What it does

A driver opens a lightweight PWA, holds a button, and says what happened: where they went, the odometer reading, fuel bought, anything that went wrong. It works with no signal — recordings queue on the phone and upload when the network returns.

On the server, the note is transcribed, then a model extracts a structured daily report (route, odometer, fuel, load, incidents). Deterministic code — never the model — validates the numbers against the vehicle's history: did the odometer move forward, is fuel use plausible against the org's baseline, is this a duplicate. Failures become alerts. If a required figure is missing or the driver mumbled it, the agent asks the driver one follow-up question instead of guessing; they answer by voice.

A supervisor gets a Today board (who reported, who didn't, what needs a second look), a report detail view with the audio, transcript and extracted fields side by side, editable fields with an audit log, alerts, a daily digest, and a CSV export. Multi-tenant: each haulage firm is its own org with its own vehicles, people and invite links.

How I built it

Next.js (App Router) on Vercel, Supabase (Postgres with row-level security on every table, Auth, Storage), Tailwind. Speech-to-text runs through Groq's Whisper endpoint; extraction and the daily digest use Gemini 3.5 Flash with structured JSON output, validated with Zod before anything touches the database.

The core design rule: the model handles language, the code handles numbers. Extraction returns raw fields and a per-field confidence; TypeScript decides what they mean. Two real moments from testing: a driver said "450 litres at 1,760 naira" and the cost — ₦792,000 — was computed in code from litres × price, not by the model. And when the same driver's odometer came through garbled in the transcript, the model scored it low-confidence and the system asked him for it rather than reconstructing a number.

The pipeline is a state machine with compare-and-set transitions (queued → transcribing → extracting → validating → ready | needs_clarification), retries with deadlines, and requeue-on-failure, so a killed function or a provider 503 never strands a report. The field app is offline-first: IndexedDB queue, idempotent uploads keyed on a client UUID, a service worker for the shell.

I designed it first (grey-box wireframes, then a token system built around one idea from the subject — mechanical odometer digits for every km, litre and naira figure) and built it in structured sessions with Claude Code, each ending in a verification checklist.

Challenges I ran into

Magic-link sign-in fails on real phones. The PKCE verifier lives in the browser that requested the link, but drivers open email in a different app. Replaced the primary flow with a 6-digit email code; the link remains a same-browser fallback. Vercel Hobby limits. Crons are daily only, so the stuck-report sweep piggybacks on every pipeline call; functions need explicit maxDuration, and Gemini rejects request deadlines under 10 seconds — a bug the rehearsal runbook caught. Pidgin and Nigerian English transcription. No hosted STT lists Pidgin. Whisper handles code-switched driver speech well enough that the extraction step recovers the fields; where it can't, the clarification loop covers it. I tested with a real driver and a real truck, outdoors.

Accomplishments that I'm proud of

A real driver, on a real phone with flaky 4G, recorded a real report that reached the supervisor board with the right fuel figure and an honest "please confirm the odometer" question. The offline queue worked in the wild before I had a chance to demo it: the test phone showed "No internet connection" the whole time and the report still arrived. Every alert a supervisor acts on is auditable — produced by a rule with a threshold, not by a model's opinion.

What I learned

Rehearsal runbooks find the bugs that unit tests don't. Deterministic validation is the difference between an "AI wrapper" and a product a business can trust. And field UX is mostly about what you leave out: three screens, one action each, thumb-reachable, readable in sunlight.

What's next for Field Ops Reporter

GPS-free trip reconstruction from odometer history, a WhatsApp Business intake so drivers don't need the app at all, Hausa transcription, and per-vehicle fuel-efficiency trends. The architecture already supports swapping the STT provider and model by configuration.

Built With

Share this project:

Updates

Submission history