01Problem
What was broken
Licence applications fail in a predictable way: the applicant submits, an officer finds an expired certificate or a name that does not match the form, and the whole thing bounces back with a vague note. Weeks pass on avoidable resubmissions. Putting an LLM in charge of the decision fixes the wrong problem; the officer must stay accountable, so the system has to catch mismatches before submission without ever deciding on anyone's behalf.
02Impact
What it does now
Fictional Food Establishment Licence service, real engineering, live at permitflow.space. Operators complete a four-section application with autosave and upload supporting documents; each document is checked against what the form says and the findings surface to both applicant and officer as advice, never a verdict. Officers attach feedback to sections or documents, request targeted resubmissions, compare revisions field by field, arrange a site visit, record a seventeen-item inspection checklist on a tablet with autosave and offline tolerance, run clarification rounds with photo evidence, and approve with a generated PDF licence. Administrators get an operations overview, an activity feed and user management, read-only on cases. 61 API routes, every one with an authorization test; 859 backend tests, 256 frontend tests, twelve Playwright workflows and an axe accessibility gate over every screen at phone, tablet and desktop widths, all blocking in CI.
03Architecture
How it is built
FastAPI, SQLAlchemy 2 and PostgreSQL in a modular monolith with a pure-Python domain layer and an explicit application state machine; React 19 with TypeScript strict, TanStack Query and Tailwind on the front. Document checks go through a swappable AI provider interface: OpenAI in production, a deterministic stand-in under test, traced with LangSmith. Hardening covers rate limits, upload magic-byte checks, image metadata stripping, per-application storage budgets and one live session per account. Prometheus metrics feed Grafana dashboards and alert rules, with a Telegram bot for on-call. Images are built once in GitHub Actions and promoted through development and production on Railway, production behind an approval step.

Modules
- Auth
- Applications
- Revisions
- Documents
- AI Verification
- Feedback
- Workflow State Machine
- Notifications
- Audit
- Licence PDF
04Intelligence
Where the AI sits
Each uploaded document is checked by an AI provider against what the application form says: expiry dates, names, addresses, licence categories. Findings come back as issues to check with a severity and a suggested action, shown to the applicant before submission and to the officer during review. The check advises; the officer decides. The provider sits behind an interface, so tests run against a deterministic stand-in and the whole suite stays fast and free of network calls.
05Decisions
Trade-offs I chose
- 01
Advice, Never a Decision
The AI check can flag an expired certificate or a mismatched name, but it never changes application state. The applicant can still submit with open findings and the officer sees the same list. Accountability stays with a human, which is the only defensible position for a licensing service.
- 02
Swappable AI Provider with a Deterministic Stand-In
Document checks call an interface, not OpenAI directly. Production uses OpenAI; tests use a stub that returns fixed findings. 859 backend tests run without a network call, and switching providers later is one implementation, not a rewrite.
- 03
Explicit State Machine in a Pure-Python Domain
Fourteen statuses and every transition between them live in one pure-Python module with no framework imports. Every status change goes through it, each revision is stored as an immutable JSON snapshot for field-by-field comparison, and the API layer cannot put an application into a state the domain does not allow.
- 04
Authorization Test per Route
61 routes, 61 authorization tests. Operators cannot read other operators' cases, officers cannot edit application content, administrators are read-only on cases. Added a route without a test and CI fails.
- 05
Offline-Tolerant Inspection on a Tablet
Site visits happen in kitchens with unreliable connectivity. The seventeen-item checklist autosaves locally and syncs when the connection returns, so an officer never loses an inspection halfway through.
- 06
Build Once, Promote Through Environments
The same container image goes from development to production on Railway behind a manual approval. What was tested is what ships. Prometheus, Grafana and a Telegram bot mean a failure pages someone instead of waiting to be noticed.