Functional Weave
Code in Python

inventory.pallet-fit

How many cartons fit per layer and per pallet (UK 1200x1000 or EUR 1200x800), within a height and weight limit.

1.0.0 · published 2026-10-03 by charlie · Anterra

Pinned by 17 tests, run in TypeScript, Python and Rust.

What it does

How many identical cartons fit on a pallet: per layer, how many layers, and in total, within a load height and an optional weight limit.

**Pallets** are data (`data/pallets.json`): the UK standard pallet, 1200 x 1000 mm, and the EUR pallet (EPAL 1), 1200 x 800 mm, both ISO 6780 sizes. Cartons may not overhang the deck.

For example

  • pallet_fit(length mm 600, width mm 400, height mm 300, weight grams 12,000, uk, 1,500, 1,000,000, true) → pallet length mm 1,200, pallet width mm 1,000, carton height mm 300, per layer 5, layers 5, per pallet 25, limited by height, load height mm 1,500, load weight grams 300,000 600x400 on a UK pallet: a plain grid gets 4 a layer, turning one row gets 5
  • pallet_fit(length mm 600, width mm 400, height mm 300, weight grams 12,000, euro, 1,500, 1,000,000, true) → pallet length mm 1,200, pallet width mm 800, carton height mm 300, per layer 4, layers 5, per pallet 20, limited by height, load height mm 1,500, load weight grams 240,000 600x400 on a EUR pallet is the classic 4 a layer
  • pallet_fit(length mm 400, width mm 300, height mm 250, weight grams 5,000, euro, 1,600, —, true) → pallet length mm 1,200, pallet width mm 800, carton height mm 250, per layer 8, layers 6, per pallet 48, limited by height, load height mm 1,500, load weight grams 240,000 400x300 on a EUR pallet: 8 a layer turned the other way, not 6

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

def pallet_fit(carton: Carton, pallet: PalletType, max_load_height_mm: int, max_load_weight_grams: Optional[int], upright: bool) -> PalletFit
cartonCartonouter dimensions and gross weight of one carton
palletPalletTypeuk (1200 x 1000) or euro (1200 x 800)
max_load_height_mmintheight available for goods above the pallet deck
max_load_weight_gramsint?weight the pallet may carry; null for no limit
uprightbooltrue keeps the carton's height vertical ("this way up"); false tries each side down
returnsPalletFit

The types it declares, generated into your project

PalletType = Literal["uk", "euro"]

PalletLimit = Literal["footprint", "height", "weight"]

@dataclass(frozen=True)
class Carton:
    """One carton, as shipped."""

    length_mm: int
    width_mm: int
    height_mm: int
    weight_grams: int

@dataclass(frozen=True)
class PalletFit:
    """The best stack found, and what stopped it growing."""

    pallet_length_mm: int
    pallet_width_mm: int
    #: the carton dimension standing vertical in the chosen stack
    carton_height_mm: int
    per_layer: int
    #: full layers the height allows
    layers: int
    per_pallet: int
    #: footprint: a carton does not fit the deck; height or weight otherwise
    limited_by: PalletLimit
    #: height of the goods as stacked, the top layer counted whole
    load_height_mm: int
    load_weight_grams: int

Your code names it in one line, in the file that uses it

from fune.inventory.pallet_fit import pallet_fit  # inventory.pallet-fit@^1
impl/python.py · 74 lines · open · raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

from typing import Optional

from .inventory_pallet_fit_data import PALLETS  ← this capability’s own data, compiled from data/pallets.json into the same file by fune build
from .inventory_pallet_fit_types import Carton, PalletFit, PalletType


def _cartons_per_layer(length: int, width: int, a: int, b: int) -> int:
    """Cartons per layer on a ``length`` x ``width`` deck, base ``a`` x ``b``,
    as the best two-block pattern: one split across the length or the width,
    each block a grid with the carton one way round.
    """
    best = 0
    i = 0
    while i * a <= length:
        best = max(best, i * (width // b) + ((length - i * a) // b) * (width // a))
        i += 1
    j = 0
    while j * b <= width:
        best = max(best, j * (length // a) + ((width - j * b) // a) * (length // b))
        j += 1
    return best


def _positive(name: str, value: int) -> None:
    if isinstance(value, bool) or not isinstance(value, int) or value <= 0:
        raise ValueError("%s must be a whole number greater than zero, received %r" % (name, value))


def pallet_fit(
    carton: Carton, pallet: PalletType, max_load_height_mm: int, max_load_weight_grams: Optional[int], upright: bool
) -> PalletFit:
    """The best stack of identical cartons on a UK or EUR pallet within the limits."""
    _positive("lengthMm", carton.length_mm)
    _positive("widthMm", carton.width_mm)
    _positive("heightMm", carton.height_mm)
    wg = carton.weight_grams
    if isinstance(wg, bool) or not isinstance(wg, int) or wg < 0:
        raise ValueError("weightGrams must be a whole number, not negative, received %r" % (wg,))
    _positive("maxLoadHeightMm", max_load_height_mm)
    mw = max_load_weight_grams
    if mw is not None and (isinstance(mw, bool) or not isinstance(mw, int) or mw < 0):
        raise ValueError("maxLoadWeightGrams must be a whole number, not negative, received %r" % (mw,))
    deck = next((p for p in PALLETS if p.code == pallet), None)
    if deck is None:
        raise ValueError('unknown pallet "%s": expected uk or euro' % (pallet,))

    l, w, h = carton.length_mm, carton.width_mm, carton.height_mm
    choices = [(h, l, w)] if upright else [(h, l, w), (w, l, h), (l, w, h)]
    best: Optional[PalletFit] = None
    for vertical, a, b in choices:
        per_layer = _cartons_per_layer(deck.length_mm, deck.width_mm, a, b)
        layers = max_load_height_mm // vertical
        per_pallet = per_layer * layers
        limited_by = "footprint" if per_layer == 0 else "height"
        if per_layer > 0 and mw is not None and wg > 0:
            by_weight = mw // wg
            if by_weight < per_pallet:
                per_pallet = by_weight
                limited_by = "weight"
        fit = PalletFit(
            pallet_length_mm=deck.length_mm,
            pallet_width_mm=deck.width_mm,
            carton_height_mm=vertical,
            per_layer=per_layer,
            layers=layers,
            per_pallet=per_pallet,
            limited_by=limited_by,
            load_height_mm=0 if per_layer == 0 else -(-per_pallet // per_layer) * vertical,
            load_weight_grams=per_pallet * wg,
        )
        if best is None or fit.per_pallet > best.per_pallet:
            best = fit
    assert best is not None
    return best

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and nothing else, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add inventory.pallet-fit
Download for Python inventory.pallet-fit-1.0.0-python.fune · 15,911 bytes sha256 a59a64d0d5d5ec99c8f0fd4d1665305ad412a55bc3e6a4c3eb4c3b34c27329fa

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./inventory.pallet-fit-1.0.0-python.fune, or fetch it from a terminal with fune pull inventory.pallet-fit@1.0.0:python.

The whole function, every language, is one file too: inventory.pallet-fit-1.0.0.fune, 24,143 bytes, sha256 32db121a67b47e361aaa3a1440444154e6a0fb75cd286a87e29836986c11e78c. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

# fune: before inventory.pallet-fit

after — your function gets the result and the arguments, and returns the final result.

# fune: after inventory.pallet-fit

replace — it requires no other capability, so there is no dependency to replace.

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show inventory.pallet-fit --steps.

# fune: step inventory.pallet-fit after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
600x400 on a UK pallet: a plain grid gets 4 a layer, turning one row gets 5 length mm 600, width mm 400, height mm 300, weight grams 12,000, uk, 1,500, 1,000,000, true → pallet length mm 1,200, pallet width mm 1,000, carton height mm 300, per layer 5, layers 5, per pallet 25, limited by height, load height mm 1,500, load weight grams 300,000
600x400 on a EUR pallet is the classic 4 a layer length mm 600, width mm 400, height mm 300, weight grams 12,000, euro, 1,500, 1,000,000, true → pallet length mm 1,200, pallet width mm 800, carton height mm 300, per layer 4, layers 5, per pallet 20, limited by height, load height mm 1,500, load weight grams 240,000
400x300 on a EUR pallet: 8 a layer turned the other way, not 6 length mm 400, width mm 300, height mm 250, weight grams 5,000, euro, 1,600, —, true → pallet length mm 1,200, pallet width mm 800, carton height mm 250, per layer 8, layers 6, per pallet 48, limited by height, load height mm 1,500, load weight grams 240,000
weight binds first: 25 cartons of 20 kg reach 500 kg in the fourth layer length mm 400, width mm 300, height mm 250, weight grams 20,000, euro, 1,600, 500,000, true → pallet length mm 1,200, pallet width mm 800, carton height mm 250, per layer 8, layers 6, per pallet 25, limited by weight, load height mm 1,000, load weight grams 500,000
upright: 500x300x200 is 8 a layer, 7 layers in 1500 mm, 56 cartons length mm 500, width mm 300, height mm 200, weight grams 1,000, uk, 1,500, —, true → pallet length mm 1,200, pallet width mm 1,000, carton height mm 200, per layer 8, layers 7, per pallet 56, limited by height, load height mm 1,400, load weight grams 56,000
laid on its side the same carton gets 60: 12 a layer, 5 layers of 300 mm length mm 500, width mm 300, height mm 200, weight grams 1,000, uk, 1,500, —, false → pallet length mm 1,200, pallet width mm 1,000, carton height mm 300, per layer 12, layers 5, per pallet 60, limited by height, load height mm 1,500, load weight grams 60,000
a carton longer than the deck does not fit at all length mm 1,300, width mm 500, height mm 500, weight grams 1,000, euro, 1,600, —, true → pallet length mm 1,200, pallet width mm 800, carton height mm 500, per layer 0, layers 3, per pallet 0, limited by footprint, load height mm 0, load weight grams 0
a carton taller than the load height makes no layers length mm 400, width mm 300, height mm 1,800, weight grams 1,000, euro, 1,600, —, true → pallet length mm 1,200, pallet width mm 800, carton height mm 1,800, per layer 8, layers 0, per pallet 0, limited by height, load height mm 0, load weight grams 0
a weight limit below one carton loads nothing length mm 400, width mm 300, height mm 250, weight grams 20,000, euro, 1,600, 10,000, true → pallet length mm 1,200, pallet width mm 800, carton height mm 250, per layer 8, layers 6, per pallet 0, limited by weight, load height mm 0, load weight grams 0
weightless cartons are never weight-limited length mm 400, width mm 300, height mm 250, weight grams 0, euro, 1,600, 1, true → pallet length mm 1,200, pallet width mm 800, carton height mm 250, per layer 8, layers 6, per pallet 48, limited by height, load height mm 1,500, load weight grams 0
Show the other 7 tests
CaseArgumentsExpected
a perfect fit: 1200x1000 fills a UK layer with one carton length mm 1,200, width mm 1,000, height mm 500, weight grams 30,000, uk, 1,000, —, true → pallet length mm 1,200, pallet width mm 1,000, carton height mm 500, per layer 1, layers 2, per pallet 2, limited by height, load height mm 1,000, load weight grams 60,000
a zero dimension is an error length mm 0, width mm 300, height mm 250, weight grams 1,000, euro, 1,600, —, true → error: lengthMm must be a whole number greater than zero
a fractional dimension is an error length mm 400, width mm 300.5, height mm 250, weight grams 1,000, euro, 1,600, —, true → error: widthMm must be a whole number greater than zero
a negative weight is an error length mm 400, width mm 300, height mm 250, weight grams -1, euro, 1,600, —, true → error: weightGrams must be a whole number, not negative
a zero load height is an error length mm 400, width mm 300, height mm 250, weight grams 1,000, euro, 0, —, true → error: maxLoadHeightMm must be a whole number greater than zero
a negative weight limit is an error length mm 400, width mm 300, height mm 250, weight grams 1,000, euro, 1,600, -5, true → error: maxLoadWeightGrams must be a whole number, not negative
an unknown pallet is an error length mm 400, width mm 300, height mm 250, weight grams 1,000, us, 1,600, —, true → error: unknown pallet "us": expected uk or euro

More from the author

**Layer pattern.** Cartons in a layer may be turned 90 degrees. The layer is the best *two-block* pattern: the deck is split once, across its length or its width, and each block is filled in a grid with the carton one way round. That includes the two plain grids, and it is where mixed patterns win: a 600 x 400 carton gets 4 a layer on a UK pallet in a plain grid, and 5 with a row turned (one of the vectors). It does not search the pinwheel and multi-block patterns of dedicated palletisation software, so on some awkward sizes it is a cautious answer, never an impossible one.

**Upright or not.** With `upright` true the carton's height stays vertical, as "this way up" cartons must. With false, each of the three faces is tried down (height, then width, then length vertical) and the first stack holding the most cartons wins.

**Limits.** `layers` is how many whole layers fit in `maxLoadHeightMm` (the height above the deck: take the pallet's own height, about 144 mm for a EUR pallet, off the vehicle or racking limit first). The pallet then holds `perLayer x layers` cartons, fewer if `maxLoadWeightGrams` is reached first; a part layer on top counts whole in `loadHeightMm`. `limitedBy` says which limit bound: `footprint` when a carton does not fit the deck at all, else `height` or `weight`. Carrier and racking limits vary (a typical UK pallet network allows about 1000 kg and 2200 mm overall), so they are arguments, not data.

Dimensions and weights are whole millimetres and grams; dimensions must be positive, weights not negative.

Sources: ISO 6780:2003 *Flat pallets for intercontinental materials handling - Principal dimensions and tolerances*; EPAL, "EPAL Euro pallet (EPAL 1)" (https://www.epal-pallets.org/eu-en/load-carriers/epal-euro-pallet), 800 x 1200 x 144 mm.

Files

PathBytes
README.md2,139
data/pallets.json217
impl/python.py3,174
impl/rust.rs4,738
impl/typescript.ts3,187
vectors.json5,513