DEV Community

Sneha Shri
Sneha Shri

Posted on

Dallas Smart Dispatch: Fuel Delivery App Development Services

Picture a dispatcher in Dallas on a busy morning. Five fuel orders are open, three trucks are on the road, and the delivery windows close at different times. Two trucks carry diesel, one carries regular, and an order in Fort Worth is due before anyone can drive there.

This is the core problem behind most fuel delivery app development services: decide which truck takes which order, and be able to explain the decision afterwards. This post builds a small version in Python so you can run it, break it and extend it. It is a teaching sketch, not a production router.

The rules come before the math

A fuel order is not a parcel. Before any distance is calculated, a truck must pass three hard checks:

  1. It carries the right fuel.
  2. It has enough fuel left on board.
  3. It can arrive before the delivery window closes.

Only trucks that pass all three get a score. This order matters, because a fast truck with the wrong fuel is not a good answer.

The data model

from dataclasses import dataclass
from math import radians, sin, cos, asin, sqrt
import hashlib
import json

AVG_MPH = 30        # assumption for mixed DFW roads; replace with learned ETAs later
SERVICE_MIN = 10    # minutes spent at each stop


@dataclass(frozen=True)
class Order:
    id: str
    lat: float
    lon: float
    fuel: str
    gallons: int
    due_min: int  # minutes from now until the delivery window closes


@dataclass
class Truck:
    id: str
    lat: float
    lon: float
    fuel: str
    gallons_left: int
    busy_min: float = 0.0  # minutes until the truck is free
Enter fullscreen mode Exit fullscreen mode

Distance, ETA and score

def miles(lat1, lon1, lat2, lon2):
    """Great-circle distance (haversine) in miles."""
    p1, p2 = radians(lat1), radians(lat2)
    dlat, dlon = p2 - p1, radians(lon2 - lon1)
    a = sin(dlat / 2) ** 2 + cos(p1) * cos(p2) * sin(dlon / 2) ** 2
    return 2 * 3958.8 * asin(sqrt(a))


def eta_min(truck, order):
    drive = miles(truck.lat, truck.lon, order.lat, order.lon) / AVG_MPH * 60
    return truck.busy_min + drive


def score(truck, order):
    """Lower is better. None means this truck cannot take the order."""
    if truck.fuel != order.fuel or truck.gallons_left < order.gallons:
        return None
    eta = eta_min(truck, order)
    if eta > order.due_min:
        return None
    # Tie-breaker: do not burn a nearly full truck on a small order.
    return eta + 0.01 * (truck.gallons_left - order.gallons)
Enter fullscreen mode Exit fullscreen mode

Notice that score returns None instead of a very large number. A missing score is a clear "no", and it keeps impossible assignments out of the results.

Greedy assignment

def assign(orders, trucks):
    plan, unassigned = [], []
    for order in sorted(orders, key=lambda o: o.due_min):
        scored = [(score(t, order), t) for t in trucks]
        scored = [(s, t) for s, t in scored if s is not None]
        if not scored:
            unassigned.append(order.id)
            continue
        _, best = min(scored, key=lambda x: x[0])
        eta = eta_min(best, order)
        plan.append((order.id, best.id, round(eta)))
        best.lat, best.lon = order.lat, order.lon
        best.busy_min = eta + SERVICE_MIN
        best.gallons_left -= order.gallons
    return plan, unassigned
Enter fullscreen mode Exit fullscreen mode

Orders are handled from the tightest window to the loosest. After each assignment the truck "moves" to the order location, is busy until the stop is finished, and has less fuel left.

Running it on a Dallas-Fort Worth morning

trucks = [
    Truck("T1", 32.7767, -96.7970, "diesel", 800),    # Dallas depot
    Truck("T2", 32.8140, -96.9489, "diesel", 500),    # Irving
    Truck("T3", 32.7357, -97.1081, "regular", 600),   # Arlington
]
orders = [
    Order("O1", 33.0198, -96.6989, "diesel", 300, 90),   # Plano
    Order("O2", 32.9126, -96.6389, "diesel", 200, 45),   # Garland
    Order("O3", 32.8998, -97.0403, "regular", 150, 60),  # DFW Airport area
    Order("O4", 32.7555, -97.3308, "diesel", 450, 40),   # Fort Worth
    Order("O5", 33.1507, -96.8236, "regular", 100, 120), # Frisco
]
plan, left = assign(orders, trucks)
for row in plan:
    print("order %s -> truck %s, ETA %d min" % row)
print("unassigned:", left)
Enter fullscreen mode Exit fullscreen mode

Output:

order O2 -> truck T1, ETA 26 min
order O3 -> truck T3, ETA 24 min
order O1 -> truck T2, ETA 41 min
order O5 -> truck T3, ETA 77 min
unassigned: ['O4']
Enter fullscreen mode Exit fullscreen mode

Order O4 in Fort Worth cannot be served in time by any diesel truck, so the engine does not guess. It returns the order as unassigned, and a human can call the customer, widen the window or send a different truck. In a real product this list is one of the most useful screens you can build.

Where AI smart dispatch fits

The greedy rule above is easy to read, but it makes two weak assumptions: a flat 30 mph speed and one order at a time. A common next step is an AI smart dispatch layer that learns travel times from your own GPS history and predicts the ETA for each truck, order and hour of the day. Keep the three hard checks as guardrails around whatever model you add, so a prediction can never send diesel to a regular order. For larger fleets with many stops per route, a vehicle routing solver such as Google OR-Tools is a better base than a greedy loop.

A tamper-evident audit log

Every assignment should leave a record that is hard to edit quietly. A hash chain is a small way to get that: each entry stores the hash of the previous entry, so changing one old row breaks everything after it.

def append_audit(log, event):
    prev = log[-1]["hash"] if log else "0" * 64
    body = json.dumps(event, sort_keys=True)
    digest = hashlib.sha256((prev + body).encode()).hexdigest()
    log.append({"event": event, "prev": prev, "hash": digest})


def verify_audit(log):
    prev = "0" * 64
    for entry in log:
        body = json.dumps(entry["event"], sort_keys=True)
        if entry["prev"] != prev:
            return False
        if hashlib.sha256((prev + body).encode()).hexdigest() != entry["hash"]:
            return False
        prev = entry["hash"]
    return True
Enter fullscreen mode Exit fullscreen mode
log = []
for order_id, truck_id, eta in plan:
    append_audit(log, {"action": "assign", "order": order_id, "truck": truck_id, "eta_min": eta})
print("audit valid:", verify_audit(log))
log[0]["event"]["truck"] = "T9"
print("audit valid after edit:", verify_audit(log))
Enter fullscreen mode Exit fullscreen mode
audit valid: True
audit valid after edit: False
Enter fullscreen mode Exit fullscreen mode

This is not a replacement for write-once storage or database permissions, but it makes silent edits easy to detect.

Who may do what

Dispatch actions should never depend on a button being hidden in the interface. In a typical setup, an API gateway validates the OAuth 2.0 access token (a JWT), and the service then checks the roles inside the validated claims:

PERMISSIONS = {
    "dispatcher": {"assign", "reassign", "read_orders"},
    "driver": {"read_own_orders", "complete_delivery"},
    "admin": {"assign", "reassign", "read_orders", "export_audit"},
}


def can(claims, action):
    return any(action in PERMISSIONS.get(r, set()) for r in claims.get("roles", []))


claims = {"sub": "u-102", "roles": ["dispatcher"]}
print("dispatcher can assign:", can(claims, "assign"))
print("dispatcher can export_audit:", can(claims, "export_audit"))
Enter fullscreen mode Exit fullscreen mode
dispatcher can assign: True
dispatcher can export_audit: False
Enter fullscreen mode Exit fullscreen mode

The can function trusts the claims it receives, so token validation (signature, expiry, audience) must happen before it runs.

Limits of this sketch

  • Greedy assignment is fast but not optimal; a different order of orders can give a better plan.
  • Straight-line distance ignores roads, tolls and traffic.
  • It models one fuel grade per truck and no compartments.
  • There is no persistence, retry logic or live location feed.

If you are choosing a partner instead of building

Teams that would rather not build this from scratch often look at fuel delivery app development services that cover dispatch, tracking, driver and customer apps and support after launch. Whether the app is custom fuel delivery app development for one fleet or B2B fuel delivery app development for several accounts, the questions are the same: how are assignments decided, how are they audited, and who can change them? Good fuel delivery app development services can answer all three in plain language and show them in a demo.

Disclosure: this article was created with the help of AI. The code was run before publishing and the outputs above are the real results. It was written by the Dev Technosys team, a software development company that builds on-demand, logistics and fuel delivery apps for clients globally.

Top comments (0)