Live Public Project

Dispatch Optimizer

A constraint-based field-service dispatch optimizer for Atlas Field Services, a synthetic commercial-facilities maintenance company. It uses a synthetic backlog, technician capacity, skill matching, SLA rules, travel penalties, and scenario controls to recommend operationally feasible job assignments, then benchmarks an OR-Tools CP-SAT optimized plan against a naive manual dispatch baseline to show measurable operational impact. This is the project in the portfolio that does not just report on a business: it makes a decision.

Dispatch Board with scenario sliders, a Dallas-Fort Worth service-region map of technicians and jobs, and the backlog list
Dispatch Board: scenario controls (crew size, traffic penalty, skill shortage, SLA strictness, emergency rate), a live service-region map, and the day's backlog. Every change is a real CP-SAT solve.
Baseline versus optimized comparison showing more jobs completed, fewer SLA breaches, less travel, and less overtime
Baseline vs Optimized: the decision quantified. Same crew, same jobs, same constraints, only the planning differs.
Optimizer Results page with recommended per-technician routes and job timing
Optimizer Results: the recommended plan, per-technician routes, and start and end timing for each assigned job.
Constraint Explorer showing reason-coded explanations for why each job was assigned or deferred
Constraint Explorer: why each decision was made, with a reason code on every unassigned job (skill, parts, capacity, or SLA trade-off).
Marginal Value of Capacity chart sweeping crew size to show the decision frontier
Marginal Value of Capacity: sweep crew size through the optimizer to chart the hire-versus-overtime decision frontier and the marginal value of each added technician.

Problem

On a typical day a field-service dispatcher faces more work than the crews can finish: twelve technicians, more than a hundred jobs, six skills, hard SLA deadlines, travel between sites, fixed shifts, limited overtime, and some jobs blocked on parts. The common-sense approach, highest priority first to the nearest qualified technician, makes locally reasonable choices that are globally poor: it strands specialists, books avoidable travel, and breaches SLAs that better sequencing would have saved.

The question is not what happened. It is what should we do next, given limited people, time, travel, skills, and SLA risk. That is an optimization problem, not a reporting problem, and it is the gap this project fills: operational decision-making under constraints rather than another dashboard.

Users or audience

The primary audience is hiring managers and technical interviewers evaluating practical operations-research and decision-support engineering: model formulation, the honesty of the comparison, live solve performance, and how constraints and trade-offs are surfaced to a non-technical operator.

The interactive demo is built for portfolio demonstration on a fully synthetic, seeded dataset. No proprietary or employer data is used. It is not a production dispatch system for a real field-service operation.

Solution

Technician dispatch is modelled as a vehicle-routing problem with time windows plus assignment and solved with Google OR-Tools CP-SAT. Per technician, a routing circuit is built over home plus candidate jobs using AddCircuit, with self-loop arcs making each job optional and arc literals carrying travel time. Start times respect travel and service sequencing, skills and certifications restrict which assignments can exist, parts-blocked jobs are never candidates, and the objective maximizes priority-weighted completion minus travel, SLA-breach, and overtime penalties. SLAs are soft by default and become hard for top-priority jobs under strict mode.

The comparison is the point. A deliberately naive manual baseline obeys the same feasibility rules, so the delta is planning quality, not different assumptions. Two techniques keep an eight-second live solve reliable: per-technician candidate capping keeps routing circuits small, and the baseline is warm-started into CP-SAT as a complete feasible incumbent, so the optimized plan never loses to the baseline even when optimality cannot be proven in the time budget. The solver also reports its optimality gap so the user knows how close the incumbent is to the proven bound.

Architecture

Seeded synthetic generatorBuilds one canonical, reproducible day as an Instance: twelve technicians, more than a hundred jobs, six skills, with sites at real Dallas-Fort Worth lat/long. Same seed produces identical data.
Pure-Python optimizer coredomain, travel, baseline, cp_sat_model, transform, metrics. Imports no web framework and no database, so the engine is fully unit-testable without infrastructure.
CP-SAT model (VRPTW plus assignment)Per-technician AddCircuit routing, travel-sequenced start times, soft or strict SLA, overtime, candidate capping, and a baseline warm start. Reports an optimality gap.
Pluggable travel providerResolved behind one seam (Instance.travel): offline haversine by default, with optional real road durations from OpenRouteService or OSRM. CP-SAT uses directional arcs, so asymmetric road times need no model changes.
SQLAlchemy persistence and SQL viewsMaster data and every solve are stored, so portable analytical views report on real runs. SQLite locally for zero infrastructure, Postgres in production, same models and views.
FastAPI on the VPSThin orchestration: /api/workload, /api/optimize, /api/runs/:id. solve_service runs transform, then baseline and CP-SAT, scores both, persists, and returns the comparison. Behind nginx with Let's Encrypt TLS.
Next.js on Cloudflare PagesStatic export (App Router, TypeScript, Leaflet map, Recharts, system light and dark). Seven pages: Dispatch Board, Optimizer Results, Baseline vs Optimized, Constraint Explorer, Marginal Value of Capacity, Scenario Simulator, and Executive Summary.

Results

On the canonical day (seed 42, default settings), the optimized plan beats the manual baseline on every operational axis at once: more jobs completed and fewer SLA breaches and less overtime, with the same crew. The exact figures move with the solver time budget, but the direction is stable.

+4 jobsCompleted on the same crew (77 to 81), while breaches and overtime both fall.
18 fewerSLA breaches per day (33 to 15) under identical constraints.
10.5 hrsOvertime removed per day (18.0 to 7.5), with travel down as well.
< 8 sBounded live CP-SAT solve, warm-started so optimized never loses to the baseline.
61Backend tests, including the optimized-never-loses guarantee and optimality-gap reporting.
12 / 110 / 6Technicians, jobs, and skills in the canonical synthetic day.

Tools used

  • Python 3.12
  • OR-Tools (CP-SAT)
  • FastAPI
  • SQLAlchemy 2
  • PostgreSQL / SQLite
  • Next.js (App Router, TypeScript)
  • Recharts
  • Leaflet
  • OpenRouteService / OSRM (optional)
  • pytest
  • ruff
  • GitHub Actions
  • nginx + Let's Encrypt
  • systemd
  • Cloudflare Pages

Key features

  • A real optimizer, not a heuristic dressed up as one: a VRPTW plus assignment model in OR-Tools CP-SAT with a bounded live solve.
  • An honest baseline foil that obeys the same feasibility rules, so the measured delta is planning quality rather than different assumptions.
  • Warm start plus per-technician candidate capping, which keeps the live solve tractable and guarantees the optimized plan never loses to the baseline.
  • Optimality-gap reporting so the operator knows how close the returned incumbent is to CP-SAT's proven bound.
  • Decision support, not just assignment: bottleneck-skill detection, the overtime-versus-SLA trade-off, and explicit reason-coded deferrals on every unassigned job.
  • Marginal Value of Capacity: a crew-size sweep through the optimizer that charts the hire-versus-overtime decision frontier.
  • Scenario Simulator: pull technicians, raise the traffic penalty, create a skill shortage, tighten SLA strictness, or spike emergencies, and re-solve live.
  • A pluggable travel provider behind one seam: offline haversine by default, with real road durations from OpenRouteService or OSRM, set server-side or per visitor with no key stored.
  • Full stack with a clean seam: a pure-Python, database-free optimizer core, SQLAlchemy persistence, portable analytical SQL views, and a seven-page dashboard.

Tradeoffs and constraints

A live, anytime solver on an eight-second budget cannot always prove optimality on the full instance. Candidate capping and the baseline warm start are the deliberate response: they shrink the search and guarantee a usable plan at least as good as the manual baseline on every solve, at the cost of occasionally leaving a small, reported optimality gap. The throughput floor keeps the optimizer from ever finishing fewer jobs than the baseline while it trims breaches, travel, and overtime above that floor.

The default travel model is offline haversine so the public demo is free, reproducible, and quota-free. Real road durations drop in through OpenRouteService or OSRM with no model change, because travel is resolved behind a single seam and the CP-SAT model already uses directional arcs that handle asymmetric road times.

Methodology

Appropriate use: portfolio demonstration of constraint-based optimization and decision support on a fully synthetic, seeded dataset.

Inappropriate use: as a production dispatch system, or as ground truth for staffing, routing, or service-level decisions in a real operation. The data is synthetic and the day is a single canonical instance.

Limitations

The optimizer plans a single canonical day from a seeded synthetic generator. It is deterministic and reproducible by design, not a forecast of real demand. SLA deadlines, durations, skills, and parts availability are modelled, but real-world frictions such as no-shows, re-work, mid-day reprioritisation, and stochastic travel are not.

The live solve is intentionally time-boxed, so on the hardest scenarios the returned plan can carry a small optimality gap rather than a proven optimum. The objective weights are sensible defaults exposed through the UI sliders; a real deployment would tune them against that operation's actual cost of a breach, an overtime hour, and a mile.

What I would improve next

Real road-network travel as the default rather than an option, with a cached matrix so quotas stay sane. Stochastic and rolling re-optimization so the plan absorbs no-shows and new emergencies through the day instead of solving one static instance. A learned or operator-tuned weighting fit to a specific operation's real costs. Multi-day and overtime-budget planning across a week. And a promotion gate that refuses to ship a model or parameter change unless it holds the optimized-never-loses guarantee across a battery of seeded scenarios.