Functional Weave
Code in Rust

logistics.container-fit@1.0.0

impl/python.py

3,555 bytes · the Python implementation · view 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, Tuple

from .logistics_container_fit_data import CONTAINERS  ← this capability’s own data, compiled from data/containers.json into the same file by fune build
from .logistics_container_fit_types import ContainerFit


def _whole(value: object) -> bool:
    return isinstance(value, int) and not isinstance(value, bool)


def container_fit(
    container: str,
    length_mm: int,
    width_mm: int,
    height_mm: int,
    carton_grams: int,
    keep_upright: bool,
    max_payload_kg: Optional[int],
) -> ContainerFit:
    """How many identical cartons fit in a container or trailer: the best block
    stow over the allowed orientations that pass the door, capped by payload."""
    for d in (length_mm, width_mm, height_mm):
        if not _whole(d) or d < 1:
            raise ValueError("carton dimensions must be 1 mm or more, received %r" % (d,))
    if not _whole(carton_grams) or carton_grams < 1:
        raise ValueError("cartonGrams must be 1 or more, received %r" % (carton_grams,))
    spec = next((c for c in CONTAINERS if c.code == container), None)
    if spec is None:
        raise ValueError(
            'unknown container "%s": expected %s' % (container, ", ".join(c.code for c in CONTAINERS))
        )
    payload_kg = spec.max_payload_kg
    if max_payload_kg is not None:
        if not _whole(max_payload_kg) or max_payload_kg < 1:
            raise ValueError("maxPayloadKg must be 1 or more, received %r" % (max_payload_kg,))
        if max_payload_kg > spec.max_payload_kg:
            raise ValueError(
                "maxPayloadKg must not exceed %d kg for %s, received %d"
                % (spec.max_payload_kg, container, max_payload_kg)
            )
        payload_kg = max_payload_kg

    l, w, h = length_mm, width_mm, height_mm
    # Upright first (as given, then turned on the floor), then the four tipped ones.
    orientations = (
        [(l, w, h), (w, l, h)]
        if keep_upright
        else [(l, w, h), (w, l, h), (l, h, w), (h, l, w), (w, h, l), (h, w, l)]
    )

    best: Optional[Tuple[int, int, int, Tuple[int, int, int]]] = None
    best_count = 0
    for o in orientations:
        a, b, c = o
        if a > spec.length_mm or b > spec.width_mm or c > spec.height_mm:
            continue
        # End-loaded containers: the carton's cross-section has to pass the door.
        if spec.door_width_mm is not None and b > spec.door_width_mm:
            continue
        if spec.door_height_mm is not None and c > spec.door_height_mm:
            continue
        along = spec.length_mm // a
        across = spec.width_mm // b
        layers = spec.height_mm // c
        count = along * across * layers
        if best is None or count > best_count:
            best = (along, across, layers, o)
            best_count = count
    if best is None:
        raise ValueError("carton does not fit in %s in any allowed orientation" % container)

    weight_limit = payload_kg * 1000 // carton_grams
    cartons = min(best_count, weight_limit)
    space = spec.length_mm * spec.width_mm * spec.height_mm
    along, across, layers, placed = best
    return ContainerFit(
        container=container,
        cartons=cartons,
        space_limit=best_count,
        weight_limit=weight_limit,
        limited_by="weight" if weight_limit < best_count else "space",
        along_length=along,
        across_width=across,
        layers=layers,
        placed_length_mm=placed[0],
        placed_width_mm=placed[1],
        placed_height_mm=placed[2],
        cargo_grams=cartons * carton_grams,
        volume_used_basis_points=cartons * l * w * h * 10000 // space,
    )