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
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.
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.
What we built
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.
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.
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.
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.
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.
Architecture
Next.js and TypeScript. Dispatch board over WebSockets, technician view as an offline-capable PWA with a local queue.
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.
PostgreSQL with PostGIS for depot and site geography and travel-time estimates. Redis for board presence and live fan-out.
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.
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.
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
Key decisions
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.
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.
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.
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