A small Django/DRF service that acts as a fleet fueling optimizer: it exposes a REST API, and will call an external raw map and routing API, processes the response to find optimal fuel stops from known gas stations (optmizes by max range and fuel price), then returns the optimized route and refueling stops map to the caller.
- On start, the fuel stations CSV data is loaded into the database, and each station gets the coordinate of the city it belongs to using another US cities CSV
- You call the endpoint with a start and finish location:
GET /api/route_map/?start=Chicago,%20IL&finish=Denver,%20CO - The API checks its cache. If this exact trip was planned before, it returns the stored answer immediately.
- It converts both locations into coordinates using a geocoding service (Nominatim), turning "Chicago, IL" into a latitude/longitude pair.
- It asks OSRM for the driving route — the full path of the trip as a list of coordinates, plus which highways the route uses.
- It narrows down the fuel stations. Out of 8K stations in the CSV, it keeps only the ones in states the route passes through.
- It walks the route and picks fuel stops. Starting from the beginning, it tracks how far the car has driven. When the remaining distance exceeds 500 miles (with a 10% safety buffer), it looks for the cheapest station near the furthest point it can still reach, and marks it as a stop. It repeats this until the destination fits within one tank.
- It calculates the total cost. Each leg of the trip is charged at the price per gallon of the station the car refueled at before that leg, using the reference MPG and the computed leg distance.
- It returns the answer: the route coordinates to draw on a map, the list of fuel stops with their names, prices, and distances, plus the total miles and total fuel cost.
The first request for a new trip takes a few seconds because the geocoding and routing services are rate-limited to one call per second. Every repeat request is instant, and any new trip that passes near stations already looked up reuses those coordinates for free, and previously computed routes are cached as well.
- We optimize the refueling stops and refueling cost. So We assume that the truck/car is loaded on fuel at start, for the first leg.
- Each fuel station gets the centroid coordinates of the city it belongs to. We reserve a fixed safety margin distance to take the detours into account.
- All internal calculations are in metrics units, the MPG and MAX RANGE are in miles for user reference, and they are internally converted to meters/km.
- Add
FuelStationandCachedRoutemodels and migrations - Register models in the Django admin
- Implement external API calls and refuel stations optimizer
- Add tests for the refuel stops optimizer service layer
- Gracefully handle the network timeout errors when calling OSRM or Nominatim APIs in the main endpoint call result.
- Docker Engine 24+ and Docker Compose v2 (
docker compose, notdocker-compose) - Optional, only for local (non-Docker) runs: Python 3.14
Verify:
docker --version
docker compose version| Layer | Choice |
|---|---|
| Language | Python 3.14 |
| Web framework | Django 6.1 |
| API framework | Django REST Framework 3.18 |
| HTTP client | requests |
| Server | Gunicorn |
| Database | SQLite (volume-backed) |
| Containerization | Docker + Docker Compose |
SQLite lives at /app/data/db.sqlite3 inside the container, backed by the
named Docker volume sqlite_data. This means:
- The database survives
docker compose downanddocker compose up. - The database is deleted by
docker compose down -v(the-vremoves volumes). - The local
./data/db.sqlite3file is intentionally git-ignored.
Environment variables are read from .env (see .env.example):
| Variable | Purpose | Default |
|---|---|---|
DJANGO_SECRET_KEY |
Django secret key — set a real one in production | unsafe-default |
DJANGO_DEBUG |
True / False |
False |
DJANGO_ALLOWED_HOSTS |
Comma-separated hostnames | localhost |
EXTERNAL_API_BASE_URL |
Base URL of the external API (empty for now) | (empty) |
EXTERNAL_API_TIMEOUT |
External API request timeout in seconds | 10 |
git clone <your-repo-url> fleet_router
cd fleet_routercp .env.example .envThen edit .env and set at least DJANGO_SECRET_KEY. Leave
EXTERNAL_API_BASE_URL empty for now.
docker compose up --build -dOn startup the container automatically:
- Runs
python manage.py migrate- creates the SQLite database and all built-in Django tables (auth, sessions, admin, content types). - Runs
python manage.py collectstatic- gathers admin static assets. - Runs
python manage.py load_fuel_stations_csv- import the known fueling stations from the /data/fuel-prices-for-be-assessment.csv file - Starts Gunicorn on port
8000.
Check that it came up:
docker compose ps
docker compose logs -f webIf you have Python 3.14 locally:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
export DJANGO_SETTINGS_MODULE=config.settings
python manage.py migrate
python manage.py runserverThe app is then available at http://localhost:8000/.
# Health check
curl http://localhost:8000/api/health/
# -> {"status": "ok"}
# Placeholder bridge endpoint (dummy JSON for now)
curl http://localhost:8000/api/route_map/?start=Chicago,%20IL&finish=Detroit,%20MI
# -> {"start":{"query":"Chicago, IL","lat":41.8755616,"lon":-87.6244212},"finish":{"query":"Detroit, MI","lat":42.3315509,"lon":-83.0466403},"route":[[41.875563,-87.624351],[41.875314,-87.624346],[41.875125,-87.624342],[41.874667,-87.624334],[41.874574,-87.624331],[41.874452,-87.624328],[41.874447,-87.624449],[41.874447,-87.624484],[41.874428,-87.625165],[41.874427,-87.625224],[41.874422,-87.625509],[41.874418,-87.625846],[41.874414,-87.625988],[41.87441,-87.626166],[41.874406] ...}You can also open the DRF browsable API in a browser: http://localhost:8000/api/route_map/ or use Postman API client.
The admin is enabled at http://localhost:8000/admin/.
Create a superuser (one time):
docker compose exec web python manage.py createsuperuserLog in with the credentials you just set.
All management commands are run inside the running container:
# Django management commands
docker compose exec web python manage.py <command>
# Examples
docker compose exec web python manage.py shell
docker compose exec web python manage.py showmigrations
docker compose exec web python manage.py createsuperuser
# Make and apply migrations after adding a model to fleet_router/models.py
docker compose exec web python manage.py makemigrations api
docker compose exec web python manage.py migrate
# Logs
docker compose logs -f web
# Restart the app
docker compose restart web
# Stop the stack (database volume is preserved)
docker compose down
# Stop and wipe the database (irreversible)
docker compose down -v| Method | Path | Description |
|---|---|---|
| GET | /api/health/ |
Liveness probe — returns a JSON {"status": "ok"} |
| GET | /api/route_map/ |
The main endpoint for refuel stops optimization (JSON) |
| GET | /admin/ |
Django admin UI (HTML) |
| GET | /api/stats |
Reports how many fuel stations are in the DB, and how many of them have been geocoded so far (JSON) |
| GET | /api/debug/cache-stats |
Exposes the in-process counters from external API services (JSON) |
The test coverage includes 2 scenarios:
- Scenario 1 — short trip, under range, no refueling stops, zero cost.
- Scenario 2 — long trip, over range, requires refueling stops.
No network calls are made during the tests. Sandboxing and factory fixtures, mocks and stubs are used instead.
Run those tests with docker compose exec web python -m pytest