LIFO Routing for Truck Compartments

What is Compartment based LIFO routing?

Trucks used in any fleet where cargo types must not mix, like fuel tankers or refrigerated multi-zone vehicles, have physically separate compartments with each of them capable of holding distinct types of goods. When modelling such vehicles using NextBillion.ai's Route Optimization API using the compartment mode, the optimizer assigns jobs to specific compartments based on goods type, respects the physical capacity of each compartment, and when compartment_lifo: true is set, the optimizer also enforces a last-in, first-out loading sequence across compartments.

Compartment mode activates automatically when any vehicle in the request defines the compartments field. Once active, it applies globally to all vehicles and jobs in the request.

Use Case: Multi-Product Fuel Tanker Delivery

A fuel distribution company operates tanker trucks that carry both petrol and diesel simultaneously in physically separated compartments. Each compartment can hold only one fuel type at a time and mixing products is not allowed. The truck must also unload in a defined sequence: the most accessible compartment must be emptied first, working towards the least accessible compartment.

The sample dispatch involves a single tanker loaded with 200 units of petrol and 600 units of diesel, serving five gas stations. The operations team needs the optimizer to:

  • Assign petrol jobs exclusively to the petrol compartment and diesel jobs to diesel compartments.
  • Respect the physical capacity of each compartment
  • Sequence deliveries so that compartments are emptied in LIFO order with the most accessible compartment unloaded first.

This is exactly the scenario LIFO Compartment feature is designed for.

Understanding Compartment Access Priority

The order of compartments in the vehicles.compartments array directly determines the LIFO unloading sequence. Index 0 is the most accessible compartment and is always unloaded first. Higher indices are progressively less accessible and are unloaded one after the other.

In our fuel tanker example, the three compartments map to the following access and unloading order:

Compartment IndexCapacityAccess PriorityGoods Loaded
0200 unitsMost accessible (first out)Petrol — 200 units
1100 unitsMiddleDiesel — 100 units
2500 unitsLeast accessible (last out)Diesel — 500 units

The optimizer uses this priority to sequence deliveries: petrol jobs (served from C0) are scheduled before diesel jobs served from C1, which in turn are scheduled before diesel jobs served from C2. This ensures the driver always accesses the physically correct compartment at each stop.

Configuring the Feature: Input Parameters

The table below summarises all new and modified parameters introduced by LIFO Compartment:

ParameterType / LocationDescription
vehicles.compartmentsarray of objectsDefines the physical compartments of the vehicle. Array order sets access priority: index 0 is the most accessible (unloaded first in LIFO mode), last index is the least accessible.
vehicles.compartments[].capacityintegerTotal capacity of this compartment.
vehicles.compartment_lifobooleanWhen true, enforces LIFO unloading order across compartments.
jobs.goods_typestringIdentifier for the type of goods in this job. Jobs sharing the same goods_type can be assigned to the same compartment. A compartment cannot hold more than one goods type at a time.
jobs.delivery / jobs.pickuparray[integer]In compartment mode, must be a single-element array. The integer value specifies the amount of goods to deliver or pick up.

Constraints and Activation Rules

Compartment mode activates implicitly when any vehicle defines the compartments field. However, following restrictions apply across the entire request:

  • All vehicles must define compartments. Mixing compartment-enabled and standard vehicles in the same request is not allowed.
  • All jobs must use the same action type i.e. either all jobs are delivery type, or all are pickup type.
  • Goods-to-compartment assignment is solver-determined. Users cannot pre-assign a goods type to a specific compartment.
  • Shipments are not supported in this mode.
  • Each job carries exactly one goods_type.
  • A compartment can hold only one goods type at a time. Even if a compartment has remaining capacity after partial delivery, a different goods type cannot be loaded into it.
  • If capacity constraints require, then a single goods_type can be shared across multiple compartments.
  • In compartment mode, the effective vehicle capacity is derived entirely from the compartment capacities while any capacities or alternative_capacities configured are ignored.

Example API Request & Response

Example API Request

The request below configures a three-compartment fuel tanker and five delivery jobs with two fuel types:

1
curl --location 'https://api.nextbillion.io/optimization/v2?key=<your_api_key>' \
2
--header 'Content-Type: application/json' \
3
--data '{
4
"description": "LIFO Fuel Delivery with Compartments",
5
"locations": {
6
"id": 1,
7
"location": [
8
"34.05756272,-118.30431094", // index 0 – Depot / Fuel Terminal
9
"34.05576927,-118.29156308", // index 1 – Petrol Delivery 1
10
"34.04734953,-118.28428717", // index 2 – Petrol Delivery 2 & Diesel Delivery 1
11
"34.04286445,-118.29150360", // index 3 – Diesel Delivery 2
12
"34.04346375,-118.30906936" // index 4 – Diesel Delivery 3
13
]
14
},
15
"vehicles": [
16
{
17
"id": "Fuel Tanker 1",
18
"start_index": 0,
19
"end_index": 0,
20
"compartment_lifo": true,
21
"compartments": [
22
{ "capacity": 200 }, // index 0 – most accessible, unloaded first
23
{ "capacity": 100 }, // index 1
24
{ "capacity": 500 } // index 2 – least accessible, unloaded last
25
],
26
"capacity": [800] // ignored in compartment mode
27
}
28
],
29
"jobs": [
30
{ "id": "Petrol Delivery 1", "location_index": 1, "goods_type": "petrol", "delivery": [100], "service": 600 },
31
{ "id": "Petrol Delivery 2", "location_index": 2, "goods_type": "petrol", "delivery": [100], "service": 600 },
32
{ "id": "Diesel Delivery 1", "location_index": 2, "goods_type": "diesel", "delivery": [100], "service": 600 },
33
{ "id": "Diesel Delivery 2", "location_index": 3, "goods_type": "diesel", "delivery": [300], "service": 600 },
34
{ "id": "Diesel Delivery 3", "location_index": 4, "goods_type": "diesel", "delivery": [200], "service": 600 }
35
],
36
"options": {
37
"objective": { "travel_cost": "distance" },
38
"routing": { "mode": "truck" }
39
}
40
}'

Example API Response

The condensed response shows the single route for Fuel Tanker 1. All 5 jobs are assigned. Each step includes a compartment_loads array showing the per-compartment state after that stop:

1
{
2
"description": "LIFO Fuel Delivery with Compartments",
3
"result": {
4
"code": 0,
5
"summary": { "cost": 8302, "routes": 1, "unassigned": 0, "distance": 8302 },
6
"routes": [
7
{
8
"vehicle": "Fuel Tanker 1", "cost": 8302,
9
"steps": [
10
{
11
"type": "start", "load": [800],
12
"compartment_loads": [
13
{ "compartment_index": 0, "goods_type": "petrol", "load": 200 },
14
{ "compartment_index": 1, "goods_type": "diesel", "load": 100 },
15
{ "compartment_index": 2, "goods_type": "diesel", "load": 500 }
16
]
17
},
18
{
19
"type": "job", "id": "Petrol Delivery 1", "load": [700],
20
"compartment_loads": [
21
{ "compartment_index": 0, "goods_type": "petrol", "load": 100 }, // -100 from C0
22
{ "compartment_index": 1, "goods_type": "diesel", "load": 100 },
23
{ "compartment_index": 2, "goods_type": "diesel", "load": 500 }
24
]
25
},
26
{
27
"type": "job", "id": "Petrol Delivery 2", "load": [600],
28
"compartment_loads": [
29
{ "compartment_index": 0, "goods_type": null, "load": 0 }, // C0 empty
30
{ "compartment_index": 1, "goods_type": "diesel", "load": 100 },
31
{ "compartment_index": 2, "goods_type": "diesel", "load": 500 }
32
]
33
},
34
{
35
"type": "job", "id": "Diesel Delivery 1", "load": [500],
36
"compartment_loads": [
37
{ "compartment_index": 0, "goods_type": null, "load": 0 },
38
{ "compartment_index": 1, "goods_type": null, "load": 0 }, // C1 empty (more accessible diesel)
39
{ "compartment_index": 2, "goods_type": "diesel", "load": 500 }
40
]
41
},
42
{
43
"type": "job", "id": "Diesel Delivery 2", "load": [200],
44
"compartment_loads": [
45
{ "compartment_index": 0, "goods_type": null, "load": 0 },
46
{ "compartment_index": 1, "goods_type": null, "load": 0 },
47
{ "compartment_index": 2, "goods_type": "diesel", "load": 200 } // 500-300=200
48
]
49
},
50
{
51
"type": "job", "id": "Diesel Delivery 3", "load": [0],
52
"compartment_loads": [
53
{ "compartment_index": 0, "goods_type": null, "load": 0 },
54
{ "compartment_index": 1, "goods_type": null, "load": 0 },
55
{ "compartment_index": 2, "goods_type": null, "load": 0 } // all empty
56
]
57
},
58
{ "type": "end", "load": [0], "distance": 8302 }
59
],
60
"delivery": [800], "distance": 8302
61
}
62
]
63
},
64
"status": "Ok"
65
}

Interpreting the Output

In compartment mode, use the compartment_loads array at each step to understand which goods are being delivered and from which compartment. The summary load field becomes a single-element array showing only the total remaining load across all compartments.

The table below traces the full route, showing the compartment state at every step:

#StepJobLoadDistanceCompartment State After Step
1Start (Depot)[800]0 mC0: petrol 200
2JobPetrol Delivery 1[700]1,386 mC0: petrol 100
3JobPetrol Delivery 2[600]3,038 mC0: null 0
4JobDiesel Delivery 1[500]3,038 mC0: null 0
5JobDiesel Delivery 2[200]4,282 mC0: null 0
6JobDiesel Delivery 3[0]6,058 mC0: null 0
7End (Depot)[0]8,302 mC0: null 0

Three patterns emerge from the route:

  • Petrol served from C0 first (steps 2–3): Compartment 0 (the most accessible) holds all 200 units of petrol. Both petrol deliveries drain C0 in sequence, emptying it completely before any diesel is touched.
  • Diesel served from C1 before C2 (step 4): Once petrol is exhausted, the optimizer moves to diesel. C1 (100 units, more accessible) is fully drained at the first diesel stop before C2 is touched, honouring the LIFO order across diesel compartments.
  • C2 drained across multiple stops (steps 5–6): The remaining 500 units of diesel in C2 are delivered across two stops: 300 units at location 3 and 200 units at location 4.

Also note that Petrol Delivery 2 and Diesel Delivery 1 share the same physical stop and the optimizer consolidates both jobs at the same location while still respecting compartment isolation: petrol is drawn from C0 and diesel from C1 in two separate service operations.

What We Learned

This example illustrates several important takeaways about how LIFO Compartment behaves in practice:

  • Compartment array order is the LIFO sequence: The optimizer reads the compartments array as index 0 to be always unloaded first. Define compartments in the physical access order of your vehicle. Getting this order wrong will produce routes that are logistically incorrect.
  • Goods-type isolation is absolute: A compartment that has been used for one goods type cannot accept a different goods type, even if it is partially empty. Plan compartment capacities to match the total volume of each goods type being carried.
  • Vehicle-level capacity is ignored in compartment mode: Even if you specify a capacity on the vehicle, the optimizer derives effective capacity entirely from compartment capacities.
  • Compartment mode is all-or-nothing across the request: Once any vehicle defines compartments, all vehicles must define compartments and all jobs must conform to compartment-mode rules specified above. You cannot selectively enable compartment mode for a subset of vehicles.

API Response Changes in Compartment Mode

The table below summarises the new and modified fields that appear in the response when compartment mode is active:

Response FieldTypeDescription
routes[].steps[].compartment_loadsarray of objectsPer-compartment loading state after each step completes. One entry per compartment, always in compartment index order. Only present in compartment mode.
compartment_loads[].compartment_indexintegerIndex of this compartment, matching the vehicle's compartments array.
compartment_loads[].goods_typestringThe goods type currently in this compartment. null if the compartment is empty.
compartment_loads[].loadintegerRemaining load in this compartment after the current steps.
routes[].steps[].loadarray[integer]In compartment mode, a single-element array showing the total load across all compartments after this step.

Explore other powerful features that the NextBillion.ai's Route Optimization API can solve seamlessly.