Cluster orders geographically. Compute optimal driver routes. Reduce fuel and emissions.
EcoRoute is a full-stack delivery management platform that solves the last-mile delivery problem: given scattered orders across a city and a fleet of drivers, how do you assign and sequence stops to minimize total travel distance?
It answers that with two algorithms built from scratch in pure Python:
| Stage | Algorithm | What it does |
|---|---|---|
| 1οΈβ£ | K-Means++ Clustering | Groups geographically close orders β one cluster per driver |
| 2οΈβ£ | Nearest Neighbor TSP | Finds an efficient visit sequence within each cluster |
No ML library wrappers. Every line of the optimization engine is explainable.
πΈ Add your screenshots below by replacing the placeholder paths.
Two-column layout β warm gradient hero panel left, sign-in card right.
Four gradient stat tiles + recent orders list + available drivers with live status dots.
Scrollable filter tabs, status pills, and inline delete for pending orders.
Split panel β controls + route cards left, live Leaflet map right. Color-coded clusters per driver.
Today's route summary + sequenced stop cards with Mark Delivered action.
Full-screen map with numbered pins, amber route polyline, and bottom-sheet current-stop panel.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β React Frontend β
β React Query Β· Zustand Β· React Hook Form Β· Leaflet.js β
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ
β HTTP + JWT Bearer token
ββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ
β FastAPI (Modular Monolith) β
β β
β /auth /orders /drivers β
β /routing /assignments β
β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β Optimization Pipeline (Pure Python) β β
β β β β
β β pending orders β β
β β β β β
β β βΌ β β
β β geo.py βββΊ kmeans.py βββΊ tsp.py βββΊ eta.py β β
β β Haversine K-Means++ NN-TSP Linear ETA β β
β β β β
β β Zero FastAPI / Motor imports β pure computation β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββββ
β Motor (async driver)
ββββββββββββββββββββββββββββΌββββββββββββββββββββββββββββββββββββ
β MongoDB 7 (Atlas in prod Β· Docker locally) β
β orders Β· drivers Β· users Β· route_plans β
β 2dsphere indexes on orders.location + drivers.location β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
1. Fetch all PENDING orders + AVAILABLE drivers from MongoDB
2. K-Means++ initialization
βββ Pick first centroid uniformly at random
βββ Each next centroid sampled β D(x)Β² (spreads centroids)
3. Iterate until convergence (max shift < 0.0001 km)
βββ Assign each order to nearest centroid (Haversine)
βββ Recompute centroid = mean(lat), mean(lng) of cluster
4. For each cluster β Nearest Neighbor TSP
βββ Start at driver GPS location (depot)
βββ Greedily pick nearest unvisited stop
βββ Repeat β O(nΒ²), ~20% above optimal
5. Compute cumulative ETA per stop
βββ ETA = 5.0 + 2.0Γkm + 3.0Γstops (linear model)
6. Bulk-write route_plans, mark orders ASSIGNED
π€ Admin
- Create delivery orders with live map coordinate preview
- View all orders with filter tabs (All / Pending / Assigned / In Transit / Delivered)
- One-click Run Optimization across all pending orders and available drivers
- Interactive Leaflet map β color-coded cluster markers + dashed driver route polylines
- Per-driver route plan cards β distance, ETA, ordered stop list
- Delete pending orders
π Driver
- View assigned route with sequenced stops and cumulative ETAs
- Full-screen map with numbered markers and amber route polyline
- Mark individual stops as delivered from list view or map bottom sheet
- Route auto-completes when all stops are done
βοΈ System
- JWT authentication with role-based access control (admin / driver)
- GeoJSON
2dsphereindexes for efficient geospatial queries - Async FastAPI + Motor β no blocking DB calls on the event loop
- React Query v5 β automatic caching, background refetch, zero
useEffectdata fetching - Zustand for minimal global auth state with
localStoragepersistence - Fully responsive β mobile sidebar becomes a slide-in drawer
| Layer | Technology | Reason |
|---|---|---|
| Backend | Python 3.11 + FastAPI | Async, fast, automatic OpenAPI/Swagger docs |
| Database | MongoDB 7 + GeoJSON | Native geospatial queries, flexible document schema |
| Async Driver | Motor 3 | Non-blocking MongoDB matching FastAPI's asyncio loop |
| Auth | JWT (python-jose) + bcrypt | Stateless, industry-standard, no session store |
| ML / Optimization | Pure Python | Every line is explainable β no black-box library calls |
| Frontend | React 18 + Vite | Fast HMR, modern JSX tooling |
| Map | Leaflet.js + React-Leaflet | Open-source, no API key required |
| Server State | TanStack React Query v5 | Caching, loading states, cache invalidation |
| Global State | Zustand | Minimal boilerplate for auth token/role |
| HTTP Client | Axios | Interceptors for JWT injection + 401 auto-redirect |
| Forms | React Hook Form | Uncontrolled inputs, performant validation |
| Container | Docker Compose | One-command MongoDB setup |
ecoroute/
βββ docker-compose.yml
β
βββ backend/
β βββ main.py # FastAPI app, CORS, router registration
β βββ config.py # Pydantic settings (reads .env)
β βββ database.py # Motor client, 2dsphere index creation
β βββ seed.py # Creates admin + 3 drivers + 15 Bengaluru orders
β βββ requirements.txt
β βββ render.yaml # Render deployment blueprint
β βββ .python-version # Pins Python 3.11 for Render
β βββ .env # Dev defaults β change JWT_SECRET before prod
β β
β βββ auth/ # JWT login, register, /me, dependencies
β βββ orders/ # CRUD + PENDINGβASSIGNEDβIN_TRANSITβDELIVERED FSM
β βββ drivers/ # List, available filter, location update
β β
β βββ routing/
β β βββ router.py # POST /optimize Β· GET /plans
β β βββ service.py # Full optimization orchestration
β β βββ schemas.py
β β βββ algorithms/ # β Pure Python, zero app imports
β β βββ geo.py # Haversine great-circle distance
β β βββ kmeans.py # K-Means++ from scratch
β β βββ tsp.py # Nearest Neighbor heuristic
β β βββ eta.py # Linear ETA model
β β
β βββ assignments/ # Driver route fetch + stop completion
β
βββ frontend/
βββ index.html
βββ vite.config.js
βββ vercel.json # SPA routing fix for Vercel
βββ package.json
βββ src/
βββ main.jsx # QueryClient, BrowserRouter, root render
βββ App.jsx # Route definitions
β
βββ api/
β βββ axios.js # JWT interceptor + 401 auto-logout
β βββ mapConfig.js # Shared tile URL + attribution
β βββ *.js # One file per backend module
β
βββ hooks/ # React Query wrappers (useOrders, useRoutesβ¦)
βββ store/ # Zustand authStore (persisted to localStorage)
β
βββ components/
β βββ layout/ # Sidebar (collapse + mobile drawer), Topbar, ProtectedRoute
β βββ map/ # DeliveryMap (display-only), ClusterMarkers, RoutePolyline
β βββ orders/ # OrderTable, StatusBadge
β βββ ui/ # Button, Input, Spinner, EmptyState
β
βββ pages/
β βββ LoginPage.jsx # Sign In / Sign Up tabs, hero panel, quick-fill chips
β βββ admin/ # Dashboard, Orders, CreateOrder, RouteOptimizer
β βββ driver/ # Dashboard (stop list), RoutePage (map + bottom sheet)
β
βββ styles/
βββ globals.css # Design tokens (CSS vars), component classes, animations
βββ map.css # Leaflet light-theme overrides
| Requirement | Version |
|---|---|
| Docker Desktop | Any recent |
| Python | 3.11 or 3.12 (not 3.13+) |
| Node.js | 18+ |
β οΈ Python version note: Dependencies are validated on 3.11/3.12. Python 3.13+ may fail to build native wheels (pydantic-core) on Windows without the full Visual C++ build tools.
git clone https://github.com/mars-alien/eco-route.git
cd eco-routedocker compose up -dMongoDB will now auto-start every time Docker Desktop opens (the service has restart: unless-stopped).
cd backend
# Create and activate virtual environment
py -3.11 -m venv .venv
.venv\Scripts\activate # Windows
# source .venv/bin/activate # macOS / Linux
# Install dependencies
pip install -r requirements.txt
# Seed demo data (1 admin + 3 drivers + 15 orders)
python seed.py
# Start the API server
python -m uvicorn main:app --reload --port 8000cd ../frontend
npm install
npm run dev| URL | What |
|---|---|
| http://localhost:5173 | React app |
| http://localhost:8000/docs | Swagger UI (interactive API explorer) |
| http://localhost:8000/redoc | ReDoc API docs |
| Role | Password | |
|---|---|---|
| π Admin | admin@ecoroute.com |
admin123 |
| π Driver 1 | driver1@ecoroute.com |
driver123 |
| π Driver 2 | driver2@ecoroute.com |
driver123 |
| π Driver 3 | driver3@ecoroute.com |
driver123 |
1. Log in as admin
βββ Dashboard: 15 pending orders, 3 available drivers
2. Go to Optimizer β click "Run Optimization"
βββ Map: orders recolor into 3 geographic clusters
Route cards appear with distance + ETA per driver
3. Log in as driver1
βββ My Deliveries: sequenced stops with ETAs
4. Click "Mark delivered" on each stop
βββ Badge turns green β
When all done β route marked COMPLETED
π Auth
| Method | Endpoint | Auth | Body / Response |
|---|---|---|---|
POST |
/api/auth/login |
None | {email, password} β {token, role, user_id, name} |
POST |
/api/auth/register |
None | {name, email, password} β creates driver account |
GET |
/api/auth/me |
Any | Returns current user from token |
π¦ Orders
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/orders |
Any | Admin: all orders Β· Driver: their assigned orders |
POST |
/api/orders |
Admin | Create order with lat/lng coordinates |
GET |
/api/orders/{id} |
Any | Single order |
PATCH |
/api/orders/{id}/status |
Driver | Advance status (IN_TRANSIT β DELIVERED) |
DELETE |
/api/orders/{id} |
Admin | Delete β PENDING orders only |
π Drivers
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/drivers |
Admin | All drivers |
GET |
/api/drivers/available |
Admin | Drivers with is_available: true |
PATCH |
/api/drivers/{id}/location |
Driver | Update GPS position |
πΊ Routing
| Method | Endpoint | Auth | Description |
|---|---|---|---|
POST |
/api/routing/optimize |
Admin | Run K-Means++ + TSP β assigns all PENDING orders |
GET |
/api/routing/plans |
Admin | All route plans |
GET |
/api/routing/plans/{id} |
Any | Single plan with stop list |
π Assignments
| Method | Endpoint | Auth | Description |
|---|---|---|---|
GET |
/api/assignments/driver/{id} |
Any | Active route plan for a driver |
PATCH |
/api/assignments/{plan_id}/stops/{idx}/complete |
Driver | Mark stop delivered |
GET |
/api/assignments/all |
Admin | All active plans summary |
π Haversine Distance geo.py
distance = 2R Β· atan2(βa, β(1βa))
where a = sinΒ²(Ξlat/2) + cos(lat1)Β·cos(lat2)Β·sinΒ²(Ξlng/2)
Why Haversine, not Euclidean?
One degree of longitude covers ~111 km at the equator but shrinks toward the poles. Raw (lat, lng) coordinates are not a flat Cartesian plane β Euclidean distance is geometrically wrong for geographic clustering.
π΅ K-Means++ Clustering kmeans.py
Standard K-Means with smarter initialization that reliably avoids poor convergence:
Initialization (K-Means++ over naive random):
1. Pick first centroid uniformly at random from orders
2. For each remaining centroid k:
- Compute D(x)Β² = squared Haversine to nearest chosen centroid
- Sample next centroid with probability β D(x)Β²
β This spreads centroids, guaranteeing better starting positions
Iteration:
1. Assign each order to its nearest centroid (Haversine)
2. Recompute centroid = mean(lat), mean(lng) of assigned points
3. Stop when max centroid shift < 0.0001 km OR max_iter reached
π£ Nearest Neighbor TSP tsp.py
Classic greedy heuristic β fast and good enough for small clusters:
1. Start at driver's current GPS position (the depot)
2. Find the nearest unvisited stop (Haversine)
3. Travel there, mark visited, update current position
4. Repeat until all stops visited
| Property | Value |
|---|---|
| Time complexity | O(nΒ²) per cluster |
| Solution quality | ~20% above optimal on random instances |
| Max stops per driver | 8 (configurable) |
| Production upgrade | Google OR-Tools CVRPTW with time windows |
β± Linear ETA Model eta.py
ETA (minutes) = 5.0 + 2.0 Γ distance_km + 3.0 Γ num_stops
| Coefficient | Value | Meaning |
|---|---|---|
| bβ (base) | 5.0 min | App startup, first-movement overhead |
| bβ (speed) | 2.0 min/km | 30 km/h average city speed |
| bβ (stop) | 3.0 min/stop | Unloading + customer confirmation time |
Production path: train on historical delivery logs with features time_of_day, day_of_week, vehicle_type using sklearn.LinearRegression.
Why a Modular Monolith and not Microservices?
FastAPI APIRouter gives clean module separation (auth, orders, drivers, routing, assignments) without separate deployable services. At this scale β a handful of drivers, hundreds of orders β microservices add network hops, distributed transaction complexity, and deployment overhead for zero benefit. The package structure makes future service extraction straightforward if scale demands it.
Why MongoDB over PostgreSQL + PostGIS?
MongoDB has native GeoJSON support with 2dsphere indexes β a proximity query is a single $nearSphere with no extension management. Route plan documents naturally embed their stops array, so fetching a driver's full route needs no join. Schema flexibility allows evolving order fields without migration files.
Why implement the algorithms from scratch?
sklearn.KMeans+OR-Toolswould solve the problem, but you can't explain what they do in a technical interview- The K-Means++ implementation is ~80 lines; the TSP heuristic is ~30 lines
- Every coefficient, distance formula, and convergence condition is visible and changeable
routing/algorithms/has zero imports from FastAPI or Motor β pure computation, fully unit-testable in isolation
Why React Query over useEffect + useState?
React Query handles loading states, error states, background refetching, cache invalidation, and deduplication automatically. useEffect for data fetching means manually reimplementing all of that β plus risk of race conditions and stale closure bugs on every component.
# backend/.env
MONGO_URL=mongodb://localhost:27017
DB_NAME=ecoroute
JWT_SECRET=change-this-in-production # β use a long random string in prod
JWT_ALGORITHM=HS256
JWT_EXPIRE_MINUTES=1440 # 24 hours| Area | Current | Production Path |
|---|---|---|
| Auth storage | localStorage (XSS risk) |
httpOnly cookies |
| JWT secret | Render env var β | Secrets manager (AWS SSM / Vault) |
| Password hashing | bcrypt β | Keep β increase cost rounds |
| ETA model | Linear formula | Train on historical logs with sklearn |
| TSP solver | Nearest neighbor | Google OR-Tools CVRPTW |
| MongoDB | Atlas M0 free tier β | Replica set with oplog |
| Frontend | Vercel (CDN) β | Custom domain + Vercel |
| API server | Render (single Uvicorn) β | Gunicorn + multiple Uvicorn workers |
| CORS | Locked to Vercel domain β | Update when domain changes |
MIT β see LICENSE for details.
Built with πΏ to make delivery routing smarter and greener.





