Field service SaaS

MeridianField Services.

Dispatch, mobile job execution and automatic invoicing for a 40-technician HVAC and plumbing operation running on spreadsheets.

Real running software, seeded with a full working day.No sign-up: you get your own sandbox to rearrange.

Sector

Field service, HVAC & plumbing

Scope

Product design, architecture, build, deployment

Stack

Go, Next.js, Postgres, PostGIS, Redis

Team

Architecture, backend, frontend, DevOps

01The situation

Context

Meridian runs 40 technicians out of three depots, covering emergency callouts and scheduled maintenance under contracts with fixed response windows.

Dispatch ran on a shared spreadsheet and a phone. Jobs reached technicians by text message, evidence of work done arrived as photos in a group chat, and invoices were written up on Friday from memory and paper notes.

The cost was not obvious in any single place. It showed up as missed response windows nobody could evidence, repeat visits for jobs sent to the wrong skill set, and roughly nine days of average delay between finishing a job and billing for it.

02What made it hard

Constraints

Signal is unreliable where the work happens

Plant rooms, basements and roofs. The technician app had to keep working through a job with no connection and reconcile afterwards, not block on a spinner.

The day changes constantly

Emergency callouts land mid-morning and reshuffle everything. Any schedule the system produced had to survive being rearranged a dozen times a day.

Response windows are contractual

Different clients carry different SLAs. Breaching one is a financial event, so the system had to surface risk before the breach rather than report it after.

Invoicing had to fit existing accounting

No migration of the finance stack. The system generates the invoice and exports it in the format their accountant already works with.

03The build

What we built

01

Live dispatch board

Every job, technician and depot on one board that updates in place as things change. Reassign by dragging. Conflicts and travel-time impossibilities surface as you move, not after you save.

02

Assisted dispatch

For any unassigned job the system ranks technicians on skill match, travel time, current load and SLA risk, then states the reason in plain language. A deterministic scorer makes the ranking; the model only writes the explanation, so the same inputs always produce the same order.

03

Technician mobile view

Today's jobs, per-job checklists, photo capture and customer signature. Fully usable offline: actions queue locally and reconcile when signal returns, with conflicts resolved against server state rather than last-write-wins.

04

Evidence and invoicing

Photos, parts used and time on site attach to the job record. Completing a job generates a draft invoice against the contract rate card and pushes it to the accounting export queue.

05

Operations analytics

Response-window risk on the current day, first-time fix rate by skill and technician, utilisation by depot, and the gap between job completion and invoice issue.

04How it fits together

Architecture

Frontend

Next.js and TypeScript. Dispatch board over WebSockets, technician view as an offline-capable PWA with a local queue.

API

Go services behind REST, with a WebSocket channel for board state. Job lifecycle modelled as an explicit state machine so illegal transitions are rejected at the edge.

Data

PostgreSQL with PostGIS for depot and site geography and travel-time estimates. Redis for board presence and live fan-out.

Async

Completion raises an invoice inline so the effect is immediate, with a periodic worker sweeping for anything the inline path missed. Invoicing is idempotent on job id, so the retry cannot bill a customer twice.

Storage

Job photography on a local volume in the demo, on versioned S3 in the CDK definition, so an original cannot be replaced after a dispute.

Infra

The live demo runs on one Ampere ARM VM: Postgres, Redis and the Go API in Docker behind Caddy, which obtains and renews its own certificate. The frontend is on Vercel. The same system is defined for AWS in CDK (VPC with isolated data subnets, RDS, ElastiCache, Fargate behind an ALB, versioned S3) and validated with cdk synth, but deliberately not deployed there: a load balancer plus a NAT gateway would cost more per month than a public demo justifies.

Go · Next.js · TypeScript · PostgreSQL · PostGIS · Redis · WebSockets · AWS CDK · S3 · Docker

05Why it is built this way

Key decisions

01

The model explains, it does not decide

Dispatch ranking is a deterministic scoring function over skill, distance, load and SLA headroom. An LLM turns the winning score into a sentence a dispatcher can agree or disagree with. This keeps the behaviour reproducible and auditable, and it means the feature degrades to a plain ranked list if the model is unavailable.

02

Offline is a data model problem, not a UI problem

The technician app queues intent (job started, checklist item completed, photo captured) rather than final state. Reconciliation replays intent against current server state, so a job reassigned while a technician was underground resolves correctly instead of silently overwriting.

03

SLA risk is computed ahead, not reported behind

Headroom is recalculated in the browser every second against the job deadline, so a job visibly slides towards breach without any network traffic, while there is still time to move someone.

06Capability

What this demonstrates

Real-time multi-user state with conflict handling

Offline-tolerant mobile capture and reconciliation

Geospatial scheduling and travel-time estimation

AI applied with a deterministic core and a clear failure mode

Document generation and third-party accounting export

Idempotent background reconciliation, safe to retry