Consolidate Shipments
Answer how many trucks could we have saved by bundling? from a scenario holding a shipment history. Shipments that were already allowed to share a truck are packed onto the fewest transports that still fit — proven to be the fewest, per group — and what comes back is the same scenario with those transports in place of the shipments, so the history and the plan sit side by side as two scenarios you can compare.
When to use this¶
- You have a year of shipment history and want to know what better bundling would have been worth, before paying anyone to study it
- You want to re-run that question with different rules — a wider date window, a bigger vehicle, a transport allowed to mix modes — and compare the answers as scenarios rather than as numbers in two run reports
- Somebody has given you a consolidation number and you want one you can defend, with the rule set stated and the arithmetic proven rather than asserted
This node does not design routes, schedule departures, or price anything. It says how few transports the same freight fits on under the rules you gave it, and it never invents a cost: the saving it reports is in transports.
What you need¶
A scenario whose transport records are a shipment history — one row per movement that happened, rather than one rate per lane. That means three fields your optimizer scenarios may not carry, and all three are on the transport record:
| Field | What it is |
|---|---|
| Reference | the shipment or transport number. Two movements on one lane on one day are told apart by this and nothing else, so without it a scenario can hold only one of them |
| Date | when the movement happened. The window is measured over it, and the weekly trend is read off it |
| Load | the measure that fills the vehicle — gross weight, loading metres, cubic volume. Say which in the load unit |
Quantity and Load are the two amounts a shipment carries — pallets and kilograms, or kilograms and loading metres — and a vehicle's limits are on one or both of them. A third would need a column a transport record does not have, which is why What one vehicle holds asks for exactly two and never a list.
The scenario states its Unit. Each shipment's quantity reaches a vehicle's limit by way of the scenario's Unit, set in Create Scenario, so a scenario holding shipments and no Unit is refused before anything is packed.
A transport record also needs a product, and a shipment export usually has
none. Map Transport refuses a row without one — a movement moved
something — so if your export has no article column, add one upstream with a
single value like Freight. Everything on the lane then agrees about it, and
What may share a vehicle leaves it alone.
The chain this node sits at the end of¶
Two workflows, and the second one is the study. The first turns the export into a scenario; the second loads it, packs it, and persists the plan as a scenario of its own.
flowchart LR
n1["Import Data"] --> n2["Filter"]
n2 --> n3["Map Transport"]
n3 --> n4["Create Scenario"]
n5["Import Data (locations)"] --> n6["Map Locations"]
n6 --> n4
n7["Import Data (products.csv)"] --> n8["Map Products"]
n8 --> n4
- Import Data — the shipment export, as it came out of the system.
- Filter — drop what cannot be consolidated at all. Sea and air legs, internal transfers between your own sites, shipments under an incoterm your planners would never bundle across. Dropping them here rather than inside the analysis is what makes the exclusion a rule a reader can see and change.
- Map Transport — name the columns. The shipping point is the source, the ship-to is the target, the transport type is the mode, the shipment number is the reference, the gross weight is the load. If the export has no article column, a Script before this step adds one:
def main(input_data):
for row in input_data:
row["product"] = "Freight"
return input_data
Freight. A shipment naming a product that is not in the list stops
Create Scenario and names the row.
6. Create Scenario — persist it, with its Unit set to the unit your
quantities are in. Call it Shipment History.
Then the study itself:
flowchart LR
n1["Load Scenario"] --> n2["Consolidate Shipments (into Scenario Data)"]
n2 --> n3["Create Scenario"]
Load Scenario reads the history back, Consolidate Shipments packs it, and Create Scenario persists the result as Consolidated Plan. You now have two scenarios over the same freight, and every comparison the product can draw between two scenarios works on them.
A canonical rule set, to start from and argue with. Nothing mixes; the bucket is the ISO week; a 24-tonne limit on the load; a window of 6 days; bundles of at least 2. Run it beside a fill-in-original-order spreadsheet rule over the identical groups and compare the transports each saves: the same freight and the same constraints, yet the node typically saves more, because a spreadsheet fills bins in whatever order the file happened to be sorted in.
Nothing has a default, and that is deliberate¶
Every setting below is required. There is no default on any of them, and the node refuses to run until each one is answered — you will see a configuration finding against the node naming what is still unset.
The reason is that these are business choices, not properties of your data. What
a vehicle holds, how far apart two shipments may be dated, whether a truck may
carry two products — each has more than one defensible answer, and the wrong one
is silent. A declared capacity of 24000 would be read as the capacity by
every agent that ever configured this node, including the ones whose client runs
40-tonne trains. So the tool asks instead, and the result states back every
answer it was given.
What may share a vehicle is the one question where choosing nothing is an answer: it means every shipment on a vehicle shares its whole lane, which is the strictest rule there is.
Settings¶
The panel asks the questions below. Each row is one question, and one of them, How close in time two shipments must be, is answered with two settings.
| Question | What you answer | What it decides |
|---|---|---|
| What may share a vehicle | Product, Transport mode, both or neither | Which parts of a lane one transport may blur. Tick neither and every shipment on a vehicle shares product and mode. Allow Transport mode and several part loads become one full one; allow Product and a truck carries two. A transport that mixes a field names none of it — a truck carrying two products carries neither alone — so the plan's transport record leaves that field blank. Source and target are never on this list: a transport leaves one place for one other. |
| What one vehicle holds | Two rows, one for what your shipments count and one for what fills the vehicle, each with a Name of your own, a Limit and its Unit | Two rows, because a shipment carries two amounts. Name each one in your own words — Pallets, Gross weight — and that name is what the utilisation comes back under. Answering one is enough: a vehicle that only ever runs out of pallet spaces has one binding dimension, and the other is left blank. Where both are answered a bundle must fit both at once, so weight and loading metres each bind on different lanes, which is the point of answering both. Each shipment is converted into the unit. A shipment's quantity is an amount of its product, so it is brought into the limit's unit with the scenario's conversions for that product, by way of the scenario's Unit: 30 pallets of one product and 600 kg of another fill one truck correctly. A shipment no conversion brings into the unit stops the run, naming the product and the units. A load is not an amount of a product — it is the gross weight or loading metres of the movement — so no product's conversion applies to it: a load reaches the limit's unit only by the built-in metric steps (t, kg, g, mg; m3, l, ml). A load with no load unit, or one those steps do not bring into the limit's unit, stops the run, naming both units. The unit is restated in the result so a reader can check it. One vehicle profile per run: comparing unlike vehicles is a question about their cost, and this node values nothing in money. |
| How close in time two shipments must be | Two settings. Bucket: None, Day, ISO week or Month. Window Days: a whole number, 0 or above | The bucket is the calendar period one transport may not cross. A week is the rule a weekly transport plan is published on, and it is what most consultancy studies state. It is also what keeps each sub-problem small enough to prove: without a bucket, a lane is every shipment that ever ran on it. Window Days is how many days apart the earliest and latest shipment in one bundle may be dated: 0 keeps a bundle inside one day, and 6 is the widest span that fits inside a calendar week. The window is a tolerance, where the bucket is a partition — you usually want both. |
| The smallest bundle worth making | A whole number, 2 or above | The fewest shipments a bundle may hold. A shipment that joins no bundle passes through unchanged as its own transport, so raising this above 2 forbids pairs rather than forbidding singletons. |
| How the saving is reported | One or more of Source, Target, Product, Transport mode | The fields the lanes ranked by consolidation gain are rolled up by. Source and target give you lanes; target alone gives you destinations. A field a transport may mix is refused here, because a transport that mixes a field cannot be filed under it. |
What comes back¶
The scenario again. Its transport records are the plan — one row per transport, with the measures summed, the lane it agreed on, the day it leaves and the lowest shipment number it carries as its reference. Everything else the scenario held travels through untouched, and so does every flow this node could not pack.
Transport Cost is carried through as the sum of what the bundled shipments were charged. A history says what the movements that happened cost; nothing in it says what one bundled truck would cost, so the node does not guess, and the saving it reports is in transports.
Saved with the plan¶
Saving the plan also saves what happened to every shipment of the history: the transport it travels on, and whether it was consolidated, travels unchanged or was excluded, with the reason for an exclusion or for a shipment too large for one vehicle. Each shipment keeps its own lane, date, product, transport mode, load and quantity beside that outcome. Each shipment and each transport is identified by its row in the history and in the plan, counted from one, so a missing or repeated shipment number is still told apart.
The plan is complete on its own. Everything its comparison reads is saved inside it, so you can download it, import it into another project and see the same figures there, and delete or replace the history it was packed from without changing the plan. Each rerun saves a new plan, leaving dashboards over an earlier plan unchanged. The rules supplied to the node stay with the run that used them; they are inputs, while what happened to each shipment is saved state.
Saving the plan also saves four report tables beside it, and they appear in the Viewer's Tables with every other table of the plan:
| Table | What it holds |
|---|---|
consolidation-summary |
Transports before and after, transports saved and their share, rows excluded |
consolidation-lanes |
The saving by source and target; sort by transports saved to find the top lanes |
consolidation-weekly |
The saving by week, each week named by its Monday |
consolidation-shipments |
Every shipment beside the transport it travels on, with that transport's date and load |
They are calculated once, when the plan is made, from its shipments and transports, and travel with it into downloads, imports and dashboards like any other table. Weekly attribution uses the earliest shipment in each transport, which can precede its departure week. Excluded rows are counted separately and never presented as transport savings.
In the Viewer, select a connection in the Consolidated Plan to open its Shipments: each original shipment with its result transport, its disposition, exclusion reason, original load and quantity. Shipment and transport row numbers identify the saved rows even when a shipment reference is missing or repeated. Use Transports for the result flow rows. A location's Connections tab offers the same shipments with Outbound and Inbound selection, and pages through all of them.
Study available during the run¶
The transient study remains available to downstream scripts under _consolidation:
| Key | What it carries |
|---|---|
method |
every rule and parameter actually applied, including the ones you did not think you were choosing |
baseline |
the picture today: one transport per shipment, with average utilisation per dimension |
consolidated |
the plan: how many transports, how many of them are bundles, and utilisation again |
comparison |
the answer — transports saved, the top lanes by gain, and a week-by-week baseline against plan |
bundles |
one row per bundle: its shipments, its dates and how full it is in every dimension |
assignment |
one row per shipment, naming the transport it travels on and how many days it waited |
excluded |
what took no part, with counts, reasons and examples |
optimality |
how many groups are proven optimal, and the gap on any that are not |
Getting the assignment out is one Scenario to Table and a Save Data. Scenario to Table takes the scenario and returns rows; Save Data then writes them as a data source you can download or paste beside your original export.
flowchart LR
n1["Load Scenario"] --> n2["Consolidate Shipments (into Scenario Data)"]
n2 --> n3["Scenario to Table (into Scenario Data)"]
n3 --> n4["Save Data"]
def main(scenario_data):
"""The column a planner pastes beside the original export."""
return scenario_data["_consolidation"]["assignment"]
The same shape reads any other part of the study — bundles for the plan,
comparison["weekly"] for the trend, excluded["unusable"] for what took no
part.
What "proven optimal" means here¶
Bin packing has a cheap lower bound — the fewest transports a group could possibly need, whatever plan you write — and a good one-pass plan. Where the two agree, that plan is optimal and nothing has to be searched. Where they do not, the node searches the sizes in between, and either finds a smaller plan or proves that none exists. A very large group whose bounds do not meet is not searched, and is reported with its gap.
That is a stronger claim than a time-limited solver's, and it is made per group rather than for the file as a whole. With a bucket set, most groups are usually proven from the bounds alone, and the study reports how many groups were proven. Where a group is large enough that the search runs out of its allowance, the group is reported with the plan it has and the size it is known not to beat — never quietly presented as the optimum. The study counts every group with a gap and lists the first few of them.
The bucket is what makes that possible. The lower bound knows about capacity and nothing about dates, so on a lane holding a year of shipments it sits far below anything the window allows and the node has to report a gap it cannot close. Set the bucket to a week and each sub-problem is one lane for one week, where the bound is usually already the answer.
What takes no part, and why nothing is dropped¶
Rows the node cannot pack are counted and reported by reason, with examples, and they take no part in any number — but every one of them is still in the scenario that comes back. An analysis that deleted part of the scenario it was handed would be a destructive edit wearing the name of a study.
- no_reference — no shipment number. This is the ordinary case rather than dirt: a flow your optimizer wrote is a rate over the horizon, not a movement, and there is nothing about a rate to consolidate
- duplicate_reference — two rows claiming one number, which is either a double export or a split delivery and this node cannot tell which
- no_lane — a blank source or target, so there is nowhere to pack it
- unreadable_date — the window cannot be measured
- unreadable_capacity — a blank, negative or non-numeric amount in a dimension you declared; never read as zero, which would put a free shipment in every truck
Separately, a shipment larger than one vehicle holds is passed through rather than excluded. It is one transport before and one transport after, so it appears in the baseline and in the plan alike and can never read as a saving. The study counts these shipments, so you can see how much of the freight no consolidation at this vehicle size can touch.
Common mistakes¶
- Wiring a table into it. This node reads a scenario. Map the export with Map Transport, persist it, and load it back — the canvas will refuse the edge before you run anything.
- Leaving out the locations. A shipment names its shipping point and its ship-to, and each must be a location of the scenario. Without Map Locations every shipment names a place the scenario does not have, and Create Scenario refuses the history.
- Leaving the product unmapped. Map Transport refuses a transport record with no product, so the mapping stops before this node ever runs. One constant column upstream, and that one product in Map Products, is the whole fix.
- Loading a scenario your optimizer produced. Its flows are rates with no date and no reference, so nothing packs and every row is excluded as no_reference. Consolidation reads a history.
- Putting the calendar week in Window Days. A week is a partition; a window
is a tolerance. A Window Days of
6does not mean the same calendar week — it means within six days of each other, which spans two weeks half the time. Set Bucket to the week and keep the window for how long freight may wait. - Leaving a conversion out. Quantities in kilograms and tonnes are related by the built-in metric steps, but quantities in pallets reach a weight limit only through a conversion for their product in Map Units & Conversions. Without one the run stops and names the product and the units.
- Stating a load limit in another unit than the loads. A load in loading metres never fills a limit in kilograms, whatever the scenario's conversions say about its product. State the load limit in the unit the shipments' load unit names, or a metric unit of the same kind.
- Reading the saving without the exclusions. Transports saved is a number about the rows that took part. The rows excluded, in the summary, say how much of the scenario did not.
Messages this node can show¶
- Carrying cost period missing or unknown
- Coordinate outside the world
- Filter cannot be read
- Filter matches no product
- Location missing from Locations
- Product missing from Products
- Product named ALL
- Recipe missing from Bill of Materials
- Required value blank
- Row stated twice
- Source and target identical
- Tags cannot be read
- Two lanes set one connection
- Unit conversion missing
- Unit not stated
The table this node makes is checked again with the rest of the scenario when Create Scenario assembles it: see messages about the scenario.
A run gives this node 5 minutes. A node that takes longer stops with Time limit reached.
Like every node, it can also stop on the checks before a run and the run's limits.