How the numbers are produced
July 30, 2026 · View on GitHub
Everything in this plugin comes out of scripts/freight.py, which does geometry
over data/pallets.csv and data/containers.csv. No network, no rate data, no
estimation at run time. This file records what the algorithms actually do, where
they are conservative, and where they can be beaten — so a figure can be defended
or corrected rather than trusted.
Verified against the checks in freight.py selftest on 2026-07-30.
Layer packing
The core question is how many l x w rectangles fit in an L x W rectangle. Two
methods are tried and the better one wins.
Guillotine lanes. Lanes run the full length of the area. Each lane picks one of the two orientations independently, so lanes may differ from each other but items inside a lane do not. Solved exactly by recursion over the remaining width, memoised.
This is not a compromise for its own sake: it is how a container is actually stuffed, and it reproduces the figures the trade quotes.
| Case | Lane result | Trade figure |
|---|---|---|
| EUR-1 1200 x 800 in a 20 ft, no clearance | 11 | 11 |
| EUR-1 in a 40 ft, no clearance | 25 | 25 |
| EUR-1 in a 40 ft, 50 mm clearance | 23 | 23 |
| EUR-1 on a 13.6 m curtainsider | 33 | 33 (the loading-metre basis) |
| CN 1200 x 1000 in a 20 ft | 9 | 9 |
| AU 1165 x 1165 in a 20 ft / 40 ft | 10 / 20 | 10 / 20 |
Bounded pinwheel search. Lanes cannot express a rotated, interlocking pattern, and on square-ish decks that pattern is the right answer: 600 x 400 on an 1100 x 1100 Asia pallet fits 4 as a pinwheel against 3 in lanes. The search uses a five-block template — four corner blocks placed rotationally plus a centre residual — with cut offsets restricted to 1, 2 or 3 item widths from each edge, which is where real pinwheels sit. Each block is then lane-packed.
The restriction keeps it in milliseconds rather than making it an optimiser. It is skipped entirely when lanes already fill 97% of the area, because on a floor holding thousands of small cartons one more unit is noise.
So the layer count is a good lower bound, not a proven optimum. A dedicated load planner may beat it by a unit on an awkward footprint. Every placement it does return is validated for overlap and overhang (up to 200 placements per layer, above which the check is skipped for speed), so it never returns a figure that cannot physically be laid out.
Where lanes are forced
For pallets going into a container the pinwheel search is disabled and the lane figure is used. A mixed or offset floor plan is valid geometry — the packer finds 10 x 1200 x 1000 in a 20 ft against 9 in lanes — but it has to be loaded in a particular order off a placement diagram, and forwarders plan on the lane figure. When the mixed plan is better, the output says so in a note instead of silently claiming the higher number.
For product on a pallet and for loose cartons in a container, pinwheels stay enabled: a packer at a bench or a stevedore hand-stowing cartons has no sequencing problem.
Stacking, nesting and build height
Layers are clear height // item height, where clear height is the build limit
less the pallet's own deck. Default build limit is 1800 mm gross.
Nesting is modelled as a pitch: the first unit costs its full height, each
further one adds height x (1 - nesting). --nesting 0.75 means a nested unit
adds a quarter of its height, which is the right shape for empty crates, buckets
and trays. This is the single input that changes an answer most — a nesting load
can be three times a stacked one — so it is never assumed.
Clearances and derates
| Allowance | Default | Why |
|---|---|---|
| Loading clearance along the container length | 50 mm | Without it the packer returns the theoretical maximum, which cannot be stuffed. 50 mm turns the geometric 12 EUR-1 in a 20 ft into the 11 the trade plans on. |
| Loose (floor-loaded) derate | 5% | Gaps at the doors, bracing, dunnage, walls that are not square. Raise to 10% for an unfamiliar loader or an awkward shape. |
| Palletised derate | 0 | The floor plan times the pallet build is already exact geometry. A buffer here double-counts the 50 mm clearance unless you have a specific reason. |
| Door clearance for a built pallet | door height less 30 mm | A pallet built taller cannot be carried in. Flagged as a note, not silently accepted. |
Weight
A load is volume-limited or weight-limited, and which one binds is often the whole answer. Geometric counts are capped by the container's payload, less the tare of the pallets themselves where the load is palletised. When the cap binds, the output says the container ships part empty and buying more volume achieves nothing.
The crossover is each unit's payload divided by its cube, and it is not one
number — it varies by more than 3x across the fleet, which is why verify prints
it per container rather than offering a rule of thumb:
| Unit | Weight binds above |
|---|---|
| 13.6 m curtainsider | 267 kg/m3 |
| 45 ft HC | 321 kg/m3 |
| 40 ft HC | 347 kg/m3 |
| 40 ft GP | 395 kg/m3 |
| 40 ft reefer HC | 433 kg/m3 |
| 20 ft GP | 849 kg/m3 |
| 20 ft reefer | 968 kg/m3 |
Those assume a perfectly cubed load; at a realistic 85% volume utilisation the effective thresholds are about 15% higher. The practical consequence is that a dense product should go in 20 ft boxes and a light bulky one in 40 ft HCs — the same cargo can be weight-limited in a 40 and volume-limited in a 20.
Pallet tare weights in pallets.csv are typical builds, not specifications, and
matter mainly for dense loads: 23 EUR-1 pallets put 575 kg of wood in a 40 ft
before any product.
Volume as billed
Freight is billed on space occupied, not on the product's own volume, so:
- Palletised, the billed volume is the pallet footprint times the built
height, air under the top layer included. A part-filled last pallet is costed
on its own shorter build rather than a full one, because that is what it
occupies and quoting
pallets x full heightoverstates the bill. - Loose, each unit is billed for its share of the column it stands in, which for a nesting product is far less than its own volume.
- Chargeable weight is the greater of actual and volumetric weight. Divisors: sea LCL 1 m3 = 1000 kg; air 167 kg/m3 (the IATA 6000 cm3/kg factor); road groupage 333 kg/m3; courier 200 kg/m3. Overridable.
Cost
cost divides rates the user supplies by a load this tool computes. It holds no
rate data and cannot look any up. Duty defaults to CIF-based (goods + freight),
which is how the EU, UK and Israel assess it; --duty-base goods covers
FOB-based regimes. Quantity breaks are applied by finding the highest break the
order quantity reaches.
The LCL/FCL crossover is container rate / (per-m3 rate x m3 per unit), capped
by what the container holds. When the crossover exceeds container capacity,
groupage wins at every quantity — worth stating plainly, because the instinct to
"just take a container" is strong and sometimes wrong.
Listing verification
verify exists because the inputs are the weak link, not the arithmetic. It
re-derives every claim a listing makes and reports the contradictions: stated CBM
against the dimensions (naming a litres/m3 confusion when the ratio says so),
gross against net, density against the payload thresholds above, units-of-measure
absurdities in both directions, packing efficiency, and how the footprint sits on
a deck with the nearest ISO 3394 module that would do better.
The check that cannot be done by reading is whether the claimed pack count physically fits the carton — run through the same packer as everything else, over every orientation of the unit, inside the carton cavity. "12 per carton" against a carton that holds 8 is a common and completely invisible error.
Two limitations to know:
- The pack-fit check assumes a plain stack. A nesting product legitimately
fits more per carton than the check allows, so it will report an impossible pack
where none exists. For nesting goods, omit
--per-cartonand take the pack count from the supplier, then pass--nestingtopalletorcontainer. - Internal-to-external conversion assumes a uniform wall (
--wall, default 5 mm per side). Real crates taper, and vendors measure the cavity at different heights — published internal figures for the same 600 x 400 mould vary by around 10% in litres depending on whether the top opening or the base was measured. Freight is charged on the external figure, so that is the one to obtain; the conversion is a fallback, not a substitute.
Known limitations
- Layer counts are a heuristic lower bound (see above).
- Mixed-SKU loads are not modelled. One product per calculation.
- No weight distribution, axle loading or VGM check. A container can be legal by payload and illegal by axle weight on the road leg.
- No stacking-strength model.
--max-layersis where a crush limit goes, and it has to come from the product's own spec. - Reefer internal dimensions vary by make more than any other row in the data;
they are marked
indicativeand should be verified against the actual unit. - Container payloads vary by build, by carrier and by the road weight limit at each end. The figures are nominal.