from typing import Optional, Tuple from .logistics_container_fit_data import CONTAINERS 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, )