Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Fuel Route API

A Django REST API that plans the cheapest way to fuel up on a road trip.

Give it a start and finish location anywhere in the USA, and it returns:

  • the driving route (as geometry, ready to draw on a map)
  • the cost-optimal set of fuel stops along the way, respecting a 500-mile vehicle range
  • the total distance, gallons, and money spent on fuel (assuming 10 mpg)

A minimal HTML front end (with a live Leaflet map) is included too, so you can try it in a browser instead of Postman.


How it works

POST /api/route/  { "start": "...", "finish": "..." }
        │
        ├─ 1. Geocode start + finish            → Nominatim   (2 calls)
        ├─ 2. Get the driving route + geometry   → OSRM        (1 call)
        ├─ 3. Find candidate fuel stations near the route → local DB query
        └─ 4. Pick the cheapest combination of stops that never exceeds
              the vehicle's range → dynamic programming

Only 3 external API calls per request, regardless of route length or how many fuel stations exist along it. That's possible because of two design decisions:

  1. Fuel stations are geocoded once, offline, not per-request. The geocode_stations management command runs against the ~8,000-row OPIS price sheet ahead of time and writes lat/lon into the database. A live request never geocodes a station — it only looks up stations already sitting in the DB within a bounding box around the route.
  2. The optimizer is a shortest-path problem, not a greedy "nearest cheap station" heuristic. Fuel stops are modeled as nodes on a line (distance-along-route), and a dynamic program finds the minimum-cost way to connect start → finish where every hop is ≤ 500 miles. This correctly handles cases where skipping a nearby expensive station in favor of a farther cheap one is the better move — a naive "always fill up at the cheapest station in range" approach can get this wrong.

Tech stack

Piece Choice Why
Framework Django 6.0.7 + Django REST Framework latest stable Django
Routing OSRM public demo server free, no API key, one call returns full route geometry + distance
Geocoding Nominatim (OpenStreetMap) free, no API key
Database SQLite zero setup; swap the DATABASES config for Postgres in production
Fuel price data Provided OPIS CSV, pre-geocoded offline avoids per-station API calls at request time

Quickstart

python3 -m venv venv
source venv/bin/activate        # Windows: venv\Scripts\activate

pip install -r requirements.txt

python manage.py migrate

# Load the pre-geocoded fuel price CSV into the database
python manage.py load_fuel_stations

python manage.py runserver

The API is now live at http://127.0.0.1:8000/.

Re-geocoding the price sheet yourself: if you want to regenerate fuel/data/fuel_prices_geocoded.csv from the raw fuel/data/fuel_prices.csv, run python manage.py geocode_stations. This is a one-time, offline data-prep step — it is not called during normal API requests. Before running it, open config/settings.py and set NOMINATIM_USER_AGENT to your own name/contact info; Nominatim's usage policy rejects generic placeholder user agents with a 403.

Test the API using Postman

Endpoint: POST /api/route/ Content-Type: application/json Headers: Content-Type: application/json

Request body:

{
  "start": "Dallas, TX",
  "finish": "Chicago, IL"
}

Response:

{
  "start": {
    "query": "Dallas, TX",
    "latitude": 32.78,
    "longitude": -96.80
  },
  "finish": {
    "query": "Chicago, IL",
    "latitude": 41.88,
    "longitude": -87.63
  },
  "total_distance_miles": 925.4,
  "vehicle_max_range_miles": 500,
  "vehicle_mpg": 10,
  "total_gallons": 92.54,
  "total_fuel_cost": 267.15,
  "fuel_stops": [
    {
      "name": "PILOT TRAVEL CENTER #123",
      "address": "...",
      "city": "...",
      "state": "OK",
      "latitude": 35.47,
      "longitude": -97.52,
      "price_per_gallon": 2.89,
      "distance_along_route_miles": 210.3,
      "gallons_purchased": 45.2,
      "cost": 130.63
    }
  ],
  "route_geometry": [
    [32.78, -96.80],
    [32.79, -96.81]
  ]
}

A ready-to-import Postman collection and environment are included in postman/.

Error responses

Status Meaning
400 Start/finish could not be geocoded, or the request body is invalid
422 The route is technically valid but no combination of known fuel stations can bridge a gap within the 500-mile range (e.g. a remote stretch with no stations in the dataset)
502 The routing service (OSRM) could not be reached

Try it in a browser

Visit http://127.0.0.1:8000/ for a simple form-based UI that renders the route and fuel stops on a Leaflet map — the same optimize_route logic backs both the HTML view and the JSON API.

Project structure

fuel-route-api/
├── config/                  # Django project settings, root URLs
├── fuel/
│   ├── models.py             # FuelStation model
│   ├── views.py               # RoutePlanView (API) + home (HTML)
│   ├── serializers.py         # DRF request/response schemas
│   ├── services/
│   │   ├── geocoding.py       # Nominatim wrapper (per-request)
│   │   ├── routing.py         # OSRM wrapper
│   │   ├── geo_utils.py       # haversine distance, route sampling, bounding box
│   │   ├── optimizer.py       # candidate station lookup + DP cost optimizer
│   │   └── route_optimizer.py # orchestrates the full pipeline
│   ├── management/commands/
│   │   ├── geocode_stations.py  # one-time offline geocoding of the CSV
│   │   └── load_fuel_stations.py # loads the geocoded CSV into the DB
│   ├── data/                  # raw + geocoded fuel price CSVs
│   ├── templates/, static/    # HTML front end
│   └── tests.py
└── postman/                  # importable Postman collection + environment

Running the tests

python manage.py test

Tests cover the DP optimizer (including cases where a cheaper-but-farther station beats a nearer-but-pricier one, and cases requiring multiple stops) and the geocoding fallback logic. They run against mocked data with no network calls, so they're fast and deterministic.

Assumptions & known limitations

  • Vehicle range (500 mi) and fuel economy (10 mpg) are fixed per the assignment spec (VEHICLE_MAX_RANGE_MILES / VEHICLE_MPG in settings.py); the vehicle always starts with a full tank.
  • Fuel stations are only considered if they're within FUEL_STATION_MAX_DETOUR_MILES (default: 5 miles) of the route line, and the optimizer's positions are based on distance-along-route rather than exact routed detour distance.
  • ~6% of stations in the provided price sheet couldn't be matched to a precise street address by Nominatim and fall back to their city center (geocode_precision = "city_fallback" on the FuelStation model).
  • The OSRM public demo server and Nominatim's public instance are rate-limited and intended for light/demo use. For production traffic, point OSRM_BASE_URL at a self-hosted OSRM instance (or a provider like OpenRouteService/Mapbox) and add caching/backoff for Nominatim.
  • DEBUG = True and a checked-in SECRET_KEY are fine for this assessment but should never ship to production as-is.

About

Django REST API that finds the cheapest fuel plan for US road trips. Input start/finish, get route geometry, cost‑optimal fuel stops (500‑mile range), and totals for distance, gallons, and cost (10 mpg). Includes minimal HTML front end with live Leaflet map for browser testing.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages