Jul 2026 – Present
One system of record for UK fire and building-safety consultancies: enquiries, projects, marked-up floorplans, issued reports and the money still owed, with every change kept. Multi-tenant, built on a temporal data model, and live as a demo.
Tech Stack
About this project
What it is
One system of record for a UK fire and building-safety consultancy: enquiries, projects, buildings, assessments, documents, marked-up floorplans, issued reports, and the debtors that follow. Multi-tenant, so it works for one practice and sells to the next.
A demo runs at planhawk.uk, with invented data and no sign-up: pick a person on the sign-in page and go. It runs end to end, from an enquiry through fees, invoices and debtors to a marked-up plan and a signed-off PDF report.
Why it needs to exist
These practices run across a scatter of tools: a CRM, spreadsheets for projects and assets, a document store, a drawing package for floorplans, and an accounts package that knows nothing about any of it. The work that ties them together is done by people remembering things.
The Building Safety Act made that expensive. A higher-risk building now needs a golden thread, a continuous auditable record of what was assessed, by whom, on what evidence, and what has changed since. PlanHawk helps the dutyholder keep theirs; a practice whose history lives in five systems and a filing convention cannot produce it on demand.
The constraint that shaped the data model
In a regulated audit trail, "what does this record say" is the wrong question. The right one is "what did it say in March, who changed it, and when". A schema that updates rows in place cannot answer that, and logging bolted on afterwards does not recover it.
So the core is temporal by construction, a pattern I first introduced at Eseye:
- Every entity is a slim live table plus a history table, and every version carries its tenant, a durable identifier, the user who made it, and the period it was true for.
- Nothing is hard deleted and no row is ever edited. Reconstructing any point in time is a query.
- Money is integer pence everywhere. An invoice total is checked against its lines, and an invoice number is spent at approval, not before.
The part that makes it a deliverable
An assessment is finished when a client holds a document they can act on and an inspector can trace, so the chain runs the whole way. A mark on a floorplan uses the standard fire-safety symbols and keeps its place whatever the zoom. The report engine renders the findings and the marked-up plan onto the practice's letterhead, goes to a named reviewer, and is issued frozen, its bytes checked against their own fingerprint.
The client sees their side through a portal: their buildings, issued reports, outstanding work, and invoices.
Built for AI assistants as well as people
Every action in PlanHawk is defined once, and that one definition is the API endpoint, the MCP (Model Context Protocol) tool, the permission and the audit event. So an AI assistant can read the practice's data through the same rules as a person. It may read and prepare drafts; it never sends or issues anything.
Working on someone else's data
The practice's real material shaped this project, which set a rule I would now apply to any client work: no repository gets a remote until an audit of both the tracked content and the full history comes back clean, and nothing derived from the real material goes back into a committed file.
One verification pass reported clean when it was not. A shell quoting slip made the search read its own pattern as a file path, so it found nothing and said so cheerfully. Every scan after that ran against a deliberately planted canary first, to prove the check could still fail before its clean result was believed.
Where it is
Four milestones are built and live on the demo: the data core and permissions, enquiry to project, the money through to an aged-debtor register, and the floorplan and report chain. What is left before a first customer is the everyday plumbing of a hosted service (scheduled jobs and real email) and the remaining report types.
Stack: Laravel, PostgreSQL, Inertia and Vue, with PDFs rendered from the same pages the user sees.
What I would do differently
- Generate the demo data synthetically from the outset. Data derived from the real thing carries information you did not intend to publish, and inference is harder to spot than a name.
- Get an independent review in earlier. One component passed its own review, then failed an independent pass on four real defects. The sharpest was a queued job whose uniqueness lock ignored the tenant, so a second tenant's work was silently discarded at dispatch.