Every 80 Kilometres, No Exceptions

A tanker carrying fuel is not allowed to wander. The operator’s safety rule says the vehicle must reach a fuel station at least every 80 kilometres — not on average, not most days, but on every route in every plan. An optimiser that produces a beautiful 95-kilometre gap between two stations has produced an unusable plan.

Rules like this (whether required by law or company policy) are everywhere in logistics, and they have almost nothing to do with the customer being served. They are about the network the vehicle moves through: which types of site it must touch, how far apart those touches can be, and how often it is allowed to come back. The Route Optimization OptiFlow API now expresses all three of these directly, as location constraints.

The rule that resets

The first is a cumulative one. You nominate a location category — fuel station, temperature-controlled hub, secure yard — and a distance. From the start of the route, the optimiser tracks kilometres driven. When the vehicle visits a location in that category, the counter goes back to zero. If the plan would ever push the counter past the limit before such a visit, that plan is rejected.

This reads naturally in operational language. A pharmacy distributor working under cold chain rules can require a controlled hub at least every 50 kilometres, because that is how long the load can safely travel between checks. A fleet with limited range can require a return to a depot within a fixed distance. In both cases the constraint holds no matter how the optimiser sequences the work, which is the point: the rule survives re-optimisation.

The rule that looks at one leg

The second constraint is narrower and, for anyone who has tried to model it, oddly hard to fake. It governs a single leg — one approach from one location to the next — and it is direction-aware. You define a starting category, an ending category and a maximum distance, and no single hop from the first to the second may exceed it.

“The next location” includes the end of the route, not only the stops in the middle of it. The leg back to base is an approach like any other, so a rule from a customer category to a depot category bounds the run home as well as the runs between stops. That matters for the operations where the last leg is the one that overruns.

This is the shape of most contractual commitments. Consecutive customer stops must never be more than 30 kilometres apart, so a technician is never stranded between appointments. The first approach out of a hub is capped, so late-running work can still be recovered. The closing leg is bounded, so drivers finish close to where they started. None of these care about total distance; they care about one specific transition, and now that transition is what you constrain.

The rule that counts

The third is the simplest to state and often the most valuable. Locations of a given category may be visited at most a set number of times per route.

Landfill sites queue, so a waste collection route capped at three tips avoids the afternoon backlog. Controlled sites log every entry, so limiting visits reduces paperwork as well as risk. Reload cycles cost time, so an operation that works best with one loop — occasionally two — can say so instead of hoping the cost model discourages a third.

Scoping the rule to the fleet it applies to

All three constraints accept an optional vehicle category. That single option is what makes them usable in mixed fleets, because real rules are rarely fleet-wide. The 80-kilometre fuel rule applies to tankers, not to the van doing spare parts. A stop cap on a controlled site may apply to articulated trucks and not to small rigid vehicles. Scope the constraint by vehicle category and one optimisation run can hold different rules for different parts of the fleet — no splitting the problem, no post-processing to reconcile the results.

How you express it in a request

All three live in the same place: the constraints.locations object of the optimization request, as three arrays — maximumDistances, maximumApproachDistances and maximumStops. Each array holds independent rule objects, and you can mix all three in one request.

Setting them up is a two-step job. First, tag the things the rules talk about: locations carry a categories list (DEPOT, FUEL_STATION, CUSTOMER — the names are yours), and vehicles carry their own categories. Then declare the constraints against those category names.

Array Fields Unit of value
maximumDistances locationCategory, value, optional vehicleCategory metres
maximumApproachDistances startLocationCategory, endLocationCategory, value, optional vehicleCategory metres
maximumStops locationCategory, value, optional vehicleCategory stops per route

Distances are always metres, so the 80-kilometre fuel rule is a value of 80000. Stop counts are per route: a maximumStops entry of 1 against a depot category allows one visit to a depot-category location per route. Where reloads are modelled as returns to that depot, that is also how you bound the number of loops a vehicle makes — though what counts as a loop depends on how your model represents the reload, so it is worth checking against a plan you know rather than assuming the mapping.

Every category field accepts negation with a leading exclamation mark. !DEPOT matches any location that is not a depot, which is usually the shorter way to say what you mean — capping stops at !DEPOT limits customer visits without enumerating customer categories. Category names are strings of letters, digits, underscores and hyphens.

One behaviour is worth knowing before you debug: if no location carries the category a constraint names, the constraint is ignored rather than rejected. That keeps requests robust when a category is absent from a particular day’s data, and it also means a misspelled category silently does nothing. When a rule appears to have no effect, check the spelling on the location’s categories first.

What to expect when a rule cannot be met

These are hard constraints, not preferences, and that is worth saying plainly. If the geography does not allow a fuel station every 80 kilometres, the optimiser will not quietly stretch to 85. What it does instead is leave the work it cannot place within the rules unscheduled, and report it as such — the plan you get back is a compliant plan plus an explicit list of what did not fit.

That is a more useful outcome than either alternative. A silently relaxed limit produces a plan that fails an audit; a wholesale rejection tells you nothing about which part of the day was the problem. A compliant plan with three unscheduled orders points straight at the constraint that is binding and the work that needs another vehicle, another day or a looser rule.

It also means the unscheduled list is the first thing to read when you tighten a limit. When you are exploring rather than enforcing, start from a value you know is achievable, tighten it, and watch what drops out.

What this lets you build

For teams integrating PTV Developer into a TMS or dispatch product, the useful part is that these rules become configuration rather than code. A safety policy, a service-level agreement or a site restriction is expressed once in the request and respected by every plan that comes back — including the re-plans that happen when the day changes. No wrapper logic splitting routes, no validation pass rejecting the optimiser’s output, no dispatcher quietly fixing the same problem every morning.

The wider context is that operational rules have multiplied faster than planning tools have. Range limits on alternative-fuel fleets, cold chain documentation, restricted-site access, contractual distance promises to end customers — all of these are now normal, and all of them are about the network rather than the order. Expressing them where the plan is actually made is what keeps a plan executable.

Learn more

Try it and contact us

By Stephan Ruppert

Principal Product Manager for Developer Components at PTV Logistics.