Heat-pump quotes with the compliance gates written into the code
From website enquiry to a fixed-price quote with no site visit: a regulated sales platform where the noise assessment, the designer's sign-off and the statutory cooling-off period are gates the system enforces. In build.

- regulatory gates, each with a failing-path test
- 3
- settings keys held as validated data
- 42
- automated tests
- 1,200+
The problem
Selling a heat pump is regulated work. A grant scheme sets what a customer can claim, the noise of the outdoor unit must be assessed against a standard before the design is approved, and consumer law gives the buyer fourteen days to change their mind before anyone is allowed on site. Missing any of those is not a bad review; it is a compliance failure.
The ambition was to remove the site visit: a homeowner describes the property, scans it with a phone, and receives a price. That moves the burden from an engineer on a doorstep to software that has to be right without anyone checking.
The business also did not yet know its own numbers: prices, grant figures, copy and margins were all still being decided. A system that bakes any of them into the code is wrong the day they change.
What we built
A job can only move through a map the code owns
A job's stage changes through one function that checks a frozen map of allowed transitions, runs the guards for the target stage and writes the change and an audit event in a single transaction, using an optimistic check so two concurrent callers cannot both advance it. There is no exit from the terminal states, and a job cannot be cancelled after installation, because the design treats the refrigerant and certification duties as outliving a cancelled order.
Gates enforced in the domain, with a failing test for each
The noise assessment must be a pass for the design currently approved: a later fail overrides an earlier pass, and a pass on a rejected or superseded design does not count. The design must be approved, and the approval is recomputed against its stored hash on retrieval. The cooling-off period is computed on the UK calendar day of signing, so a signature at 23:30 in summer counts as that day, with the earliest install at midnight UK time after the fourteenth day. Each gate has a test for the path where it blocks.
A sign-off that shows when it has been tampered with
Approving a design hashes a canonical form of its identity, version, key figures, approver, timestamp and the hash of the stored design-pack bytes, so swapping the file in storage fails verification. A database trigger then refuses edits to an approved design, permits only the move to superseded once a successor exists, blocks deletes and takes an advisory lock so two simultaneous approvals cannot both win.
An audit log that refuses to be rewritten
The audit table refuses update, delete and truncate for every role including its own owner. A plain revoke does not bind a table's owner in Postgres, so the protection is statement-level triggers, proved by a test that tries and fails. The migration lists what it cannot stop rather than implying it is airtight.
Business rules as validated data, with no fallback in code
Grant rules, price bands, the kit list, VAT, savings assumptions and every line of customer copy live in settings rows, forty-two registered keys, each validated on read. A missing or malformed setting stops the quote rather than producing a plausible wrong number, and where a fact is unknown at enquiry the assumption is made in the customer's favour and shown on screen. Cost price is kept from the browser three ways: margin is applied server-side, a strict allow-list defines the public fields, and a test plants sentinel values and fails if one appears anywhere in the output.
Scanning a room with a phone, interpreted by the server
A native module captures the room with the phone's LiDAR and uploads the model; it does no interpretation. The server works out the dominant wall angle, the smallest wall-aligned rectangle and a rectangularity score that flags L-shapes and bays, and it caps confidence below the threshold for standard routing, so a designer reads every scanned survey until the scans have been checked against a tape measure. Survey links carry only a hash in the database and travel in a header, never a URL, and the server recomputes every distance the phone shows.
One wrapper for every third-party call
Every call out (property registers, address lookup, mapping, SMS, email, smart-meter consent) goes through one wrapper that gives it a deterministic idempotency key, retries with jittered backoff and, on final failure, raises a task for the office instead of throwing. Failures are therefore visible to staff and typed for callers, and credentials are optional at build time so an environment can deploy before every vendor account exists.

The outcome
Enquiry to indicative price is built and working, and remote survey and design is well advanced. Quote and signature, and scheduling, installation and commissioning, are not yet started. The build is staged in numbered packages, each written in its own agent session.
Three regulatory gates, an append-only audit trail and a tamper-evident design sign-off are enforced below the interface, with over a thousand automated tests across the web and mobile apps, many of them against a real database.
Prices, grant figures and copy are placeholders by design: the business can set them in settings, and the system refuses to quote without them rather than guess.
Honest limits: the LiDAR and AR measuring flows are built but ship switched off until they have run on real devices, several data adapters are written to published specifications but not yet called live, and nothing is in front of customers.
Built with
- Next.js
- TypeScript
- Prisma
- PostgreSQL
- Supabase
- Expo
- React Native
- Apple RoomPlan
- Zod
- Twilio
- Resend
- Vercel
Page copy drafted with AI assistance. Skofi is accountable for it. Editorial policy
Have something shaped like this?
If any part of the above resembles a problem you are living with, a short call is the fastest way to find out whether it is the same problem underneath.
What happens on the call
- 15 minutes, no deck
- We map the process on the call
- A written scope, or an honest no
