A Django REST API that solves the Capacitated Vehicle Routing Problem with Time Windows (CVRPTW) for last-mile logistics — unified delivery and pickup stops on the same route, no paid API keys required.
POST /address/optimize/
│
│ stops_csv (address, AWB, demand, lat, lon, stop_type)
▼
┌─────────────────────────────────────────────────┐
│ VRP Solver (scripts/solver/) │
│ │
│ 1. Build N×N time matrix (Haversine + speed) │
│ 2. Construct initial routes (PATH_CHEAPEST_ARC) │
│ 3. Improve with GUIDED_LOCAL_SEARCH │
│ (penalises overused arcs → escapes optima) │
└──────────────────────┬──────────────────────────┘
│ dict[vehicle_id → list[RoutedStop]]
▼
Django REST API writes Orders to DB
│
▼
GET /address/csvDetails/ ← DP mobile app
GET /address/analytics/ ← Admin dashboard
- Unified CVRPTW — pickups and deliveries in a single solve; pickups interleave naturally mid-route
- GUIDED_LOCAL_SEARCH — measurably better than
AUTOMATICon 20–60 stop problems; escapes local optima by penalising overused arcs - Configurable per-request — every solver parameter (speed, shift length, fleet size, time limit) is an optional API param; nothing is hardcoded
- Time-of-day speed — pass
departure_hourto engage Bangalore rush-hour profiles; response includesspeed_profilefor immediate feedback - Role-based access — DP sees own orders; AD sees all; PA runs optimization
- Free geocoding — Nominatim / OpenStreetMap for CLI mode; no API key needed
- Analytics endpoint — completion rates, per-driver breakdown, capacity utilization
| Layer | Technology |
|---|---|
| API framework | Django 4.x + Django REST Framework |
| VRP solver | Google OR-Tools (CVRPTW) |
| Distance model | Haversine formula → integer time matrix |
| Geocoding (CLI) | geopy + OpenStreetMap Nominatim |
| Auth | DRF Token Authentication |
| Database | SQLite (dev) / PostgreSQL via DATABASE_URL |
| Deployment | Gunicorn + WhiteNoise |
# 1. Clone and create virtualenv
git clone <repo-url>
cd Grow_Simplee_backend
python -m venv venv && source venv/bin/activate
# 2. Install dependencies
pip install -r requirements-dev.txt
# 3. Configure environment
cp .env.example .env # Edit SECRET_KEY at minimum
# 4. Migrate and seed demo accounts
python manage.py migrate
python manage.py seed_demo # creates driver50/51/52, admin, parent (password: demo1234)
# 5. Run
python manage.py runserver # http://localhost:8000Seed + run sample optimization in one command:
python manage.py seed_demo --reset --run-sample| Method | Endpoint | Role | Description |
|---|---|---|---|
POST |
/login/register/ |
Open | Register account |
POST |
/login/token/ |
Open | Get auth token |
GET |
/login/me/ |
Any | Current user info |
POST |
/address/csvDetails/ |
DP / AD | Orders by date with optional filters |
POST |
/address/optimize/ |
PA | Run VRP, persist routes, return summary |
GET |
/address/analytics/ |
AD / PA | Aggregated metrics for a date |
POST |
/address/feeder/ |
PA | Import CLI-generated CSVs from output_files/ |
All protected endpoints require Authorization: Token <token>.
| Param | Type | Default | Description |
|---|---|---|---|
stops_csv |
file | sample_stops.csv | Unified CSV: address, AWB, demand, latitude, longitude, stop_type |
num_vehicles |
int | auto | Override fleet size |
vehicle_capacity |
int | 640 000 | cm³ per vehicle |
speed_kmh |
float | 22.0 | Fixed rider speed (ignored if departure_hour set) |
departure_hour |
int 0–23 | — | Enables Bangalore rush-hour speed profile |
shift_hours |
float | 6.0 | Working shift length |
solver_time_limit_secs |
int | 300 | OR-Tools hard stop |
driver_emails |
str | all DPs | Comma-separated emails for assignment order |
clear_today |
str | "true" |
Replace existing orders for today |
Sample response:
{
"status": "completed",
"date": "2026-03-22",
"total_orders": 37,
"vehicles": 3,
"speed_profile": {
"speed_kmh": 10.0,
"mode": "time_of_day",
"profile": "morning_rush (07:00–10:00)",
"departure_hour": 8
},
"routes": [
{
"vehicle": 0, "driver": "driver50",
"stops": 13, "deliveries": 10, "pickups": 3,
"total_time_secs": 4320
}
]
}{
"date": "2026-03-22",
"total_orders": 37,
"deliveries": 29,
"pickups": 8,
"completed": 14,
"pending": 23,
"completion_rate": 0.378,
"vehicles_active": 3,
"avg_stops_per_vehicle": 12.3,
"per_driver": [
{ "driver": "driver50", "stops": 13, "deliveries": 10,
"pickups": 3, "completed": 5, "pending": 8 }
]
}Greedy nearest-neighbour assigns each stop to the closest vehicle as stops arrive, producing routes 30–50% longer than optimal on typical Bangalore cluster distributions. CVRPTW models vehicle capacity and time windows simultaneously, allowing OR-Tools to explore thousands of candidate routes and enforce constraints globally.
It's a greedy construction heuristic: starting from the depot, always extend the current route by the cheapest available arc (shortest travel time to an unvisited stop). This produces a feasible initial solution in milliseconds — fast, but often locally trapped because it never "looks ahead" past the next stop.
AUTOMATIC uses the solver's default local search strategy, which for this problem class tends to run simulated annealing and get stuck in the same neighbourhood. GUIDED_LOCAL_SEARCH (GLS) adds a penalty to arcs that appear frequently in local optima, forcing the solver to explore qualitatively different route structures. On 20–60 stop inputs, GLS typically reduces total route time by 12–18% compared to AUTOMATIC within the same time budget.
The original 3-pass design (delivery → pickup → second_pickup) solved each wave independently. Stops assigned to wave 1 were invisible to wave 2, so a driver might drive past a pickup location twice. The unified solve gives OR-Tools visibility of all stops simultaneously — pickups are interleaved naturally between deliveries on the least-cost route.
A road distance matrix requires a mapping API (Google Maps, OSRM) — either paid or self-hosted. Haversine (crow-flies distance) divided by speed gives a lower-bound travel time; the solver's time limit compensates for inaccuracy by finding good solutions within the Haversine metric. For a production system, swapping matrix.py to call OSRM would improve accuracy with no changes to the solver or API.
When departure_hour is set, the solver uses observed urban delivery speeds:
| Time window | Profile | Speed |
|---|---|---|
| 07:00–10:00 | morning_rush |
10 km/h |
| 10:00–16:00 | off_peak |
25 km/h |
| 16:00–20:00 | evening_rush |
12 km/h |
| 20:00–07:00 | night / early_morning |
22 km/h |
The selected profile and effective speed are returned in speed_profile for immediate feedback. Omit departure_hour (or set speed_kmh) to use a fixed speed throughout.
# Run optimizer on input_files/sample_stops.csv, write to output_files/
cd scripts && python3 route.py
# Custom fleet and timing
python3 route.py --num-vehicles 3 --speed-kmh 25 --shift-hours 8
# Import CLI output into DB
python3 reader.py --alldelivery/ Django project settings
login/ Account model, DRF token auth, 3 user roles
address/ Location + Order models, views, serializers
scripts/
solver/
constants.py Named constants + Bangalore speed profiles
types.py VRPNode, VRPConfig, RoutedStop dataclasses
matrix.py Haversine time_calculator + create_time_matrix
optimizer.py Unified solve_vrp() — single OR-Tools CVRPTW solve
geocoding.py Nominatim geocoding for CLI mode
output.py CLI CSV writer
route.py optimize_from_dataframe() API entry point + CLI
reader.py Imports output_files/Final_Driver*.csv into DB
input_files/ sample_stops.csv (38 rows, 3 Bangalore clusters)
.env.example Environment variable template
| Variable | Default | Description |
|---|---|---|
SECRET_KEY |
— | Django secret key (required) |
DEBUG |
False |
Enable debug mode |
ALLOWED_HOSTS |
localhost,127.0.0.1 |
Comma-separated |
DATABASE_URL |
SQLite | PostgreSQL URL in production |
CORS_ALLOWED_ORIGINS |
http://localhost:3000 |
Comma-separated |
python manage.py test --verbosity=2 # 37 testsCovers: auth flows, role-based filtering, optimizer mocking, rush-hour speed profiles, analytics aggregation, solver error handling.
MIT