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.

CaseLane resultTrade figure
EUR-1 1200 x 800 in a 20 ft, no clearance1111
EUR-1 in a 40 ft, no clearance2525
EUR-1 in a 40 ft, 50 mm clearance2323
EUR-1 on a 13.6 m curtainsider3333 (the loading-metre basis)
CN 1200 x 1000 in a 20 ft99
AU 1165 x 1165 in a 20 ft / 40 ft10 / 2010 / 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

AllowanceDefaultWhy
Loading clearance along the container length50 mmWithout 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) derate5%Gaps at the doors, bracing, dunnage, walls that are not square. Raise to 10% for an unfamiliar loader or an awkward shape.
Palletised derate0The 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 palletdoor height less 30 mmA 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:

UnitWeight binds above
13.6 m curtainsider267 kg/m3
45 ft HC321 kg/m3
40 ft HC347 kg/m3
40 ft GP395 kg/m3
40 ft reefer HC433 kg/m3
20 ft GP849 kg/m3
20 ft reefer968 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 height overstates 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-carton and take the pack count from the supplier, then pass --nesting to pallet or container.
  • 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-layers is 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 indicative and 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.