Web application for the Capacitated Vehicle Routing Problem (CVRP) using the Column Elimination algorithm. Upload fleet and delivery data, compute travel times, run the solver, and view optimized routes on an interactive map.
- Upload locations via CSV (addresses and/or lat/lng)
- Geocoding: Nominatim (free) or Google (with API key)
- Distance matrix: haversine (free) or Google Distance Matrix (with API key)
- Column Elimination solver with real-time progress over WebSocket
- Interactive Leaflet map, route table, and solver log
| Layer | Technology |
|---|---|
| Frontend | React, TypeScript, Vite, Tailwind CSS, Leaflet, Recharts, Zustand, React Query |
| Backend | Python 3.11+, FastAPI, SQLAlchemy (async), Celery, Redis |
| Database | PostgreSQL |
| APIs | Optional: Google Maps Geocoding + Distance Matrix; default: Nominatim + haversine |
- Node.js 18+ and npm
- Python 3.11+
- PostgreSQL 16 (or use Docker)
- Redis (or use Docker)
- Docker and Docker Compose (optional, for full stack)
If you see ECONNREFUSED or "http proxy error: /problems" in the Vite terminal: the frontend is running but the backend is not. Start the backend as below.
Works with no Docker, no PostgreSQL, no Redis. The app uses SQLite and runs the solver in-process.
-
Backend (one terminal):
cd backend pip install -r requirements.txt python -m uvicorn app.main:app --host 0.0.0.0 --port 8000Or on Windows:
.\run_server.ps1from thebackendfolder.Do not set
DATABASE_URLorREDIS_URL(or leaveREDIS_URLempty). Defaults: SQLite file./vrp.db, solver runs in the same process. -
Frontend (another terminal):
cd frontend npm install npm run dev -
Open http://localhost:5173. Upload a CSV, set K and Q, then Create and run. The run may take 30–60 seconds (solver runs in-process; no live progress in the UI).
For live solver progress and production-like setup.
1. Database and Redis
docker compose up -d postgres redisWait ~10 seconds for Postgres to be ready.
2. Backend
cd backend
cp .env.example .envEdit .env and set:
DATABASE_URL=postgresql+asyncpg://vrp_user:vrp_pass@localhost:5432/vrp_dbREDIS_URL=redis://localhost:6379/0
Then:
pip install -r requirements.txt
alembic upgrade head
uvicorn app.main:app --host 0.0.0.0 --port 8000In a second terminal, start the Celery worker:
cd backend
celery -A app.celery_app worker -l info3. Frontend
cd frontend
npm install
npm run devOpen http://localhost:5173. The Vite dev server proxies /api and /ws to the backend.
| Variable | Description | Default |
|---|---|---|
DATABASE_URL |
PostgreSQL URL (use postgresql+asyncpg:// for async) |
postgresql+asyncpg://vrp_user:vrp_pass@localhost:5432/vrp_db |
REDIS_URL |
Redis URL for Celery broker/backend | redis://localhost:6379/0 |
GOOGLE_MAPS_API_KEY |
Google Geocoding + Distance Matrix key | (empty) |
GEOCODING_PROVIDER |
nominatim (free) or google |
nominatim |
SECRET_KEY |
App secret (e.g. JWT) | change-me-in-production |
SOLVER_TIME_LIMIT_SECONDS |
Max solver runtime | 60 |
SOLVER_MAX_NODES |
Max decision diagram nodes | 50000 |
SOLVER_OPTIMALITY_GAP |
Stop when gap below this | 0.005 |
MAPS_BATCH_SIZE |
Origins/destinations per Google Matrix request | 10 |
UPLOAD_DIR |
Directory for uploaded files | uploads |
With no Google API key: geocoding uses Nominatim (1 req/sec), and the distance matrix uses haversine (straight-line). Suitable for testing.
| Variable | Description |
|---|---|
VITE_API_URL |
Backend base URL (e.g. http://localhost:8000). Omit to use dev proxy /api. |
VITE_WS_URL |
WebSocket base URL. Omit to use same host as the app. |
From the project root:
docker compose up -d postgres redis
docker compose up -d backend
docker compose up -d celery_worker
docker compose up -d frontend- API: http://localhost:8000
- Frontend: http://localhost:5173
- Docs: http://localhost:8000/docs
Ensure backend/.env exists (or pass env in compose) with at least DATABASE_URL and REDIS_URL for the backend and worker.
Required columns:
| Column | Description |
|---|---|
location_id |
Unique string (e.g. STOP_001) |
address |
Full address (optional if lat/lng provided) |
latitude |
Decimal degrees (optional if address provided) |
longitude |
Decimal degrees (optional if address provided) |
demand |
Units to deliver; use 0 for depot |
is_depot |
true or false; exactly one row must be true |
Validation rules:
- Exactly one row with
is_depot = true - All demands ≥ 0
- Total demand ≤ K × Q (number of vehicles × vehicle capacity)
- Each row has either a non-empty
addressor bothlatitudeandlongitude
- Number of vehicles (K) – integer, ≥ 1
- Vehicle capacity (Q) – max demand per vehicle (homogeneous fleet)
- Problem name – optional label
| Method | Path | Description |
|---|---|---|
| POST | /problems |
Create problem (multipart: CSV + form) |
| GET | /problems |
List problems |
| GET | /problems/{id} |
Get problem status |
| DELETE | /problems/{id} |
Delete problem |
| POST | /problems/{id}/run |
Start solver (returns task_id) |
| GET | /problems/{id}/solution |
Get latest solution |
| GET | /problems/{id}/solver-log |
Solver iteration log |
| GET | /solutions/{id} |
Get solution by ID |
| GET | /tasks/{task_id} |
Celery task status |
| WS | /ws/problems/{id}/progress |
Solver progress stream |
- Homogeneous fleet – same capacity Q for all vehicles
- Single depot – all routes start and end at the same depot
- No time windows – objective is distance only
- Scale – practical up to ~75 stops; beyond ~100 use heuristic/early-stop
- Demands – integers or simple decimals recommended
Sample CSVs are in test-uploads/:
| File | Description | Suggested K | Q |
|---|---|---|---|
small_5_stops.csv |
5 locations (1 depot + 4), paper-style instance | 2 | 3 |
medium_10_stops.csv |
10 stops with lat/lng | 2–3 | 15 |
addresses_only.csv |
Addresses only (tests Nominatim geocoding) | 2 | 10 |
sample_with_labels.csv |
Same as small with clear labels | 2 | 3 |
Use New problem in the app, upload one of these files, set K and Q, then Create and run. For the small instance, total demand is 5; use K=2, Q=3 so that 2×3 ≥ 5.
frontend/ # Vite + React app
backend/ # FastAPI app
app/
main.py # FastAPI app, CORS, routes
config.py # Settings from env
database.py # Async SQLAlchemy engine/session
models.py # SQLAlchemy models
schemas.py # Pydantic schemas
csv_parser.py # CSV parse + validation
celery_app.py # Celery app
tasks.py # Celery pipeline (geocode → matrix → solver)
services/maps.py # Geocoding + distance matrix
solver/column_elimination.py # Column Elimination solver
routers/ # problems, solutions, tasks
websocket.py # Progress WebSocket
alembic/ # Migrations
tests/ # Solver unit test
test-uploads/ # Sample CSV files for PoC
docker-compose.yml
| Symptom | Cause | Fix |
|---|---|---|
ECONNREFUSED or "http proxy error: /problems" in Vite |
Backend not running on port 8000 | Start backend: cd backend then uvicorn app.main:app --host 0.0.0.0 --port 8000. |
pip install fails with "Rust not found" / "pydantic-core" |
Old pydantic needs Rust to build on Python 3.13 | Use the updated requirements.txt (pydantic>=2.10). If it still fails, run: pip install aiosqlite "pydantic>=2.10" "pydantic-settings>=2.0" then pip install -r requirements.txt. Or use Python 3.11/3.12. |
ModuleNotFoundError: No module named 'aiosqlite' |
aiosqlite not installed (needed for SQLite) | Run: pip install aiosqlite then start the backend again. |
| Backend fails on startup (password/auth) | Wrong DATABASE_URL or local Postgres user |
For no-Docker, don't set DATABASE_URL (SQLite default). Or use Docker: docker compose up -d postgres redis. |
| Upload returns 500 | Server error; check response details | Backend returns the exception in the error details. Restart backend after code changes. |
Backend solver test:
cd backend
pip install pytest
python -m pytest tests/test_column_elimination.py -vUse as needed for the project.