An operations-research portfolio project: a multi-depot technician routing model (MILP + ALNS metaheuristic) wrapped in a live, interactive dispatch control tower — built around four field-service problems that are documented in industry and research but not solved by commercial dispatch tools.
▶ Live demo: enislucas.github.io/lumenops — no install, runs entirely in your browser. Model document: docs/model.pdf.
Case study, fictitious data. The scenario is modeled on how city-scale connected street-lighting networks are operated: luminaires report their own faults, and maintenance crews must visit and fix them under municipal SLAs. Every number in this repository is synthetic, generated by a seeded script; all asset, brand, and crew names are fictional. No affiliation with any lighting-systems vendor or logistics operator is implied.
git clone <this repo>
cd lumenops/app
python -m http.server 8000 # any static server works
# open http://localhost:8000
Double-clicking app/index.html also works (the optimizer then runs on the main thread instead of a
Web Worker). No build step, no dependencies, no backend — the entire optimizer runs in your browser.
Two-minute demo script
- The app optimizes the day on load: 6 crews, 68 open work orders, 3 depots across Noord-Brabant. Watch the convergence chart (Optimizer tab) — you are watching an Adaptive Large Neighborhood Search escape local optima in real time.
- Click any bar on the dispatch board (or a map marker) → "Why this crew?" — the plan defends itself: marginal cost of the current slot, every alternative crew priced, infeasible crews ruled out with the binding constraint.
- Press ▶ and let the day run at ×120. At 09:45 a traffic jam hits; at 10:20 a critical fault (exposed wiring) is reported; at 12:00 a crew's van breaks down.
- Each disruption offers Re-optimize — the proposal shows KPI deltas and a plan-nervousness score with a churn diff (what moved, what's new). Accept it or reject it like a dispatcher would. Raise the plan stability slider and re-run to see churn priced against travel time.
- Switch Day to Normal operations (45 orders — slack exists) and toggle opportunistic preventive maintenance: crews with slack pick up nearby high-failure-risk assets (hatched bars), only where it costs zero SLA impact. On the overloaded backlog day the optimizer correctly refuses to do preventive work — capacity goes to the fire-fight. That contrast is the point.
- Snapshot plans under different weights (Scenarios tab) and compare trade-offs side by side.
Most routing demos stop at "shortest route found." This one is built around four gaps that
practitioners and the literature document as open — each is a first-class feature, with sources in
docs/industry-context.md:
| Documented gap | What LumenOps does |
|---|---|
| Plan nervousness — dispatchers reject re-plans that churn the schedule (UPS ORION pilots saw ~20–30% driver rejection; "consistent VRP" is a research field, not a product feature) | Churn is an objective term with a UI dial; every re-plan gets a nervousness score, a move-by-move diff, and an accept/reject workflow |
| Explainability — "why did job X go to crew Y?" is unanswerable in commercial tools; first academic framework only appeared in 2024 (RouteExplainer, PAKDD) | Decision-level counterfactuals: every alternative crew priced or ruled out by its binding constraint, rendered in dispatcher language |
| Opportunistic preventive maintenance — predictive modules raise separate work orders → a second truck roll, instead of using slack in today's routes | Assets above a failure-risk threshold are inserted into route slack, guarded to add zero lateness to real jobs |
| Graceful degradation — hard SLA constraints make optimizers return "infeasible" exactly on overloaded days | Critical attendance is hard, timing is soft and graded; the KPI strip forecasts SLA breaches hours ahead; a hard-SLA toggle shows the trade-off |
The model is a multi-depot VRP with time windows, skills, crew-size requirements, soft SLAs and
mandatory critical coverage. It started as a university proposal, was reviewed by a professor, and
was then corrected and extended — the full formulation and a point-by-point changelog mapping
every reviewer annotation to its fix is in docs/model.md, with a print-ready
version in docs/model.pdf. Highlights:
- Coverage was missing from the original objective — with
≤ 1visit constraints and a pure travel-time objective, the optimum is to serve nothing. Fixed with a priority-scaled unserved-job penalty (making it a team-orienteering variant). - Critical jobs: hard attendance (
Σₖ yᵢₖ = 1), soft graded lateness — the reviewer's suggested fix, adopted and defended (hard deadlines ⇒ infeasibility exactly when the tool is needed most). - The nonlinear
max(0, ·)lateness was linearized; big-M got a concrete tight definition; the declared-but-unusedcrew_requiredand skills became an eligibility matrix; shift-start constraints and depot-departure relaxation were added.
Two solvers, one model:
model/milp_reference.py |
app/js/solver.js |
|
|---|---|---|
| Method | Exact MILP (PuLP / CBC) | ALNS: regret-2 construction; random/worst/Shaw removal; adaptive operator weights; simulated-annealing acceptance |
| Role | Ground truth on small instances (~14 jobs, 3 crews) | Full 68-job day in seconds, in the browser, deterministic under a fixed seed |
| Validation | Independent feasibility checker re-derives all times | Test suite (app/js/solver_test.js); on the reference instance the heuristic matches the CBC-proven optimum exactly (2335.1 vs 2335.1, ratio 1.000) |
app/ the control tower (vanilla JS, no build step; Leaflet vendored)
js/solver.js ALNS optimizer — pure library, runs in browser & Node
js/solver_test.js test suite (node app/js/solver_test.js)
data/ seeded synthetic dataset generator + CSVs (data dictionary in data/README.md)
model/ exact MILP reference implementation + solved example
docs/model.md corrected formulation + reviewer-annotation changelog
docs/industry-context.md the four gaps, with sources; dataset realism notes
docs/contracts.md internal interface contracts between components
MILP modeling and the discipline to fix a flawed formulation · metaheuristic design (ALNS) · model validation against an exact baseline · synthetic-data engineering with domain grounding · decision-support UX (explainability, stability, human-in-the-loop replanning) · dependency-free front-end engineering (SVG charts, Web Worker with graceful fallback).
Author: Enis Lucas Ziadin. Built as a portfolio project for operations research & AI roles.