Slotting Architecture · 9 min read

How to Score Cube Utilization Per Location

You have a location master with rated cube and a live on-hand feed with product dimensions, and you need one honest number per slot: what fraction of this location’s usable volume is actually working? The naive answer — occupied cube over rated cube — is wrong twice over. Rated cube includes beam and flue clearance the slot can never fill, and even a perfectly managed slot cannot tessellate real cartons to 100%, so dividing by the raw volume guarantees every location scores low and looks like it needs consolidating. This page builds the single-location scorer that gets it right, discounting both clearance and a packing-efficiency factor so that a well-packed slot reads near 1.0. It is the focused how-to behind the Space & Cube Utilization Scoring cluster.

Decomposition of a location's rated cube into occupied, fillable, packing loss, and clearance loss A horizontal bar representing 100 percent of a location's rated cube, split into four segments left to right: occupied product at 45 percent, open and fillable space at 30 percent, packing loss at 13 percent, and clearance loss at 12 percent. A bracket above spans the first three segments, 88 percent, labelled usable cube, gross minus clearance. A lower bracket spans the first two segments, 75 percent, labelled effective capacity, usable times packing efficiency. The utilization score is occupied divided by effective capacity, 0.45 over 0.75, equalling 0.60. One location's rated cube, decomposed usable cube = gross × (1 − clearance) — 88% effective capacity = usable × packing efficiency — 75% occupied 45% open / fillable 30% packing loss (unfillable geometry) — 13% clearance loss (beam / flue) — 12% utilization = occupied ÷ effective = 0.45 ÷ 0.75 = 0.60 Dividing by effective capacity — not raw rated cube — lets a well-packed slot reach a score near 1.0.

Prerequisites

Confirm each of these before wiring the scorer into a reporting job:

  • Python 3.10+ — the implementation uses X | None unions, list[...] generics, and a dataclass for the result.
  • A location master — rated gross_cube_ft3 and a measured or defaulted clearance_factor per rack type, exported from the WMS.
  • A live on-hand feedoccupied_cube_ft3 per location, derived from unit dimensions times on-hand quantity, measured to the same envelope as gross cube.
  • A packing-efficiency estimate — the best realistic fill fraction for the storage medium, from a stacking study or a conservative 0.85 default for mixed cartons.
  • Consistent units — gross and occupied cube in the same unit (cubic feet here); a unit mismatch is the fastest way to a nonsense score.

Configuration Block

Every tunable lives in one externalized profile keyed by rack type, so a pallet lane and a shelf bin can carry different clearance and packing assumptions without touching code. The two levers that decide the score are clearance_factor (volume the slot structurally cannot use) and packing_efficiency (volume real cartons cannot tessellate into).

# cube_score.yaml — one profile per rack/storage type
selective_pallet:
  clearance_factor: 0.12      # beam + flue + handling clearance as a fraction of gross
  packing_efficiency: 0.85    # best realistic fill; score normalizes so this reads ~1.0
  over_cube_tolerance: 0.02   # occupied may exceed effective by this before it is flagged
shelf_bin:
  clearance_factor: 0.06
  packing_efficiency: 0.78    # loose eaches tessellate worse than palletized cases
  over_cube_tolerance: 0.05
# Equivalent Python config dict consumed by the scorer
CUBE_SCORE = {
    "selective_pallet": {
        "clearance_factor": 0.12,
        "packing_efficiency": 0.85,
        "over_cube_tolerance": 0.02,
    },
    "shelf_bin": {
        "clearance_factor": 0.06,
        "packing_efficiency": 0.78,
        "over_cube_tolerance": 0.05,
    },
}

Implementation

The scorer is one focused function. It computes usable cube from gross net of clearance, discounts that to effective capacity with the packing-efficiency factor, then divides occupied cube by effective capacity to produce a score where 1.0 means “as full as this medium realistically packs.” It returns a typed result carrying the intermediate volumes so the caller can trend reclaimable cube, not just the ratio, and it flags the two anomalies that signal bad input: an over-cubed slot and a non-positive capacity.

from __future__ import annotations

import logging
from dataclasses import dataclass

logger = logging.getLogger("space.cube_score")


@dataclass(frozen=True)
class CubeScore:
    location_id: str
    utilization: float          # occupied / effective capacity, clamped to [0, 1]
    effective_cube_ft3: float   # usable cube after the packing-efficiency discount
    reclaimable_cube_ft3: float # effective capacity currently empty
    flag: str                   # ok | over_cube | no_capacity


def score_location_cube(
    location_id: str,
    gross_cube_ft3: float,
    occupied_cube_ft3: float,
    clearance_factor: float,
    packing_efficiency: float,
    over_cube_tolerance: float = 0.02,
) -> CubeScore:
    """Score one location's cube utilization as occupied / (usable × packing efficiency)."""
    usable = gross_cube_ft3 * (1.0 - clearance_factor)
    effective = usable * packing_efficiency

    if effective <= 0.0:
        logger.warning("Non-positive effective cube at %s (gross=%.1f, clr=%.2f, pack=%.2f)",
                       location_id, gross_cube_ft3, clearance_factor, packing_efficiency)
        return CubeScore(location_id, 0.0, 0.0, 0.0, "no_capacity")

    ratio = occupied_cube_ft3 / effective
    flag = "ok"
    if ratio > 1.0 + over_cube_tolerance:
        logger.warning("Over-cube at %s: occupied %.1f > effective %.1f (ratio %.2f)",
                       location_id, occupied_cube_ft3, effective, ratio)
        flag = "over_cube"

    utilization = min(max(ratio, 0.0), 1.0)
    reclaimable = max(effective - occupied_cube_ft3, 0.0)
    logger.info("Scored %s: U=%.3f, reclaimable=%.1f ft3 [%s]",
                location_id, utilization, reclaimable, flag)
    return CubeScore(location_id, utilization, effective, reclaimable, flag)

Step-by-Step Walkthrough

  1. Net out structural clearance. usable = gross_cube_ft3 * (1 - clearance_factor) removes the beam, flue, and handling clearance the slot can never carry inventory in. Reading clearance_factor from the rack-type profile keeps a shelf bin (little clearance, ~0.06) and a pallet lane (more, ~0.12) honest against the same code.
  2. Discount to effective capacity. effective = usable * packing_efficiency accounts for the geometry real cartons cannot tessellate into. This is the step that makes the score reachable: because the denominator is what the medium realistically packs, a well-run slot lands near 1.0 instead of being permanently capped in the 0.8s.
  3. Guard the denominator. A gross_cube_ft3 of zero, a clearance_factor of 1.0, or a packing_efficiency of 0.0 all drive effective capacity to zero. The function returns a no_capacity flag rather than dividing by zero, so one malformed master row never aborts a facility-wide scoring pass.
  4. Divide and detect over-cube. occupied / effective is the raw ratio. A value above 1 + over_cube_tolerance means occupied cube exceeds what the slot can hold — almost always a dimension or unit error upstream — so it is flagged over_cube before the score is clamped to [0, 1].
  5. Report reclaimable cube. reclaimable_cube_ft3 is the empty portion of effective capacity. It is the number that actually drives right-sizing: a 0.40 utilization on a 120-cubic-foot lane exposes ~72 reclaimable cubic feet, which is what the re-slotting economics weigh against the move cost.

Verification

Assert the scorer’s invariants directly — a full slot reads near 1.0, an over-cubed slot is flagged not crashed, and a degenerate master row returns no_capacity. These checks run without any live feed.

import logging

logging.basicConfig(level=logging.INFO)

# A pallet lane packed to its realistic maximum should score ~1.0, not ~0.75.
full = score_location_cube("A-01", gross_cube_ft3=100.0, occupied_cube_ft3=75.0,
                           clearance_factor=0.12, packing_efficiency=0.85)
assert 0.99 <= full.utilization <= 1.0, full.utilization

# A half-empty lane exposes reclaimable cube for the re-slotting break-even.
half = score_location_cube("A-02", gross_cube_ft3=100.0, occupied_cube_ft3=33.0,
                           clearance_factor=0.12, packing_efficiency=0.85)
assert round(half.utilization, 2) == 0.44, half.utilization
assert half.reclaimable_cube_ft3 > 40.0

# A dimension error must flag, not crash.
bad = score_location_cube("A-03", gross_cube_ft3=100.0, occupied_cube_ft3=200.0,
                          clearance_factor=0.12, packing_efficiency=0.85)
assert bad.flag == "over_cube" and bad.utilization == 1.0

# A degenerate master row must degrade gracefully.
degen = score_location_cube("A-04", gross_cube_ft3=0.0, occupied_cube_ft3=0.0,
                            clearance_factor=0.12, packing_efficiency=0.85)
assert degen.flag == "no_capacity"
print(f"OK — full={full.utilization:.2f}, half={half.utilization:.2f}, "
      f"reclaimable={half.reclaimable_cube_ft3:.0f} ft3")

Sample expected output:

INFO:space.cube_score:Scored A-01: U=1.000, reclaimable=0.0 ft3 [ok]
INFO:space.cube_score:Scored A-02: U=0.444, reclaimable=42.6 ft3 [ok]
WARNING:space.cube_score:Over-cube at A-03: occupied 200.0 > effective 74.8 (ratio 2.67)
OK — full=1.00, half=0.44, reclaimable=43 ft3

Common Pitfalls

  • Dividing by rated cube instead of effective capacity. Skip the packing-efficiency discount and every location caps out in the low 0.8s, so real over-packing hides and healthy slots read as under-filled. Always normalize against effective capacity so 1.0 is genuinely reachable.
  • A packing efficiency copied across media. Palletized cases tessellate far better than loose eaches; using 0.85 for a shelf bin that really packs at 0.78 overstates its capacity and understates its utilization. Keep one packing_efficiency per rack type in the config, not a single global constant.
  • Occupied and gross measured to different envelopes. If occupied cube comes from tight case dimensions but gross is measured to the rack rails, the ratio is biased low everywhere and every slot looks like a consolidation candidate. Measure both to the same usable envelope and reconcile a sample by hand.
  • Ignoring the over-cube flag. An over_cube result is not a full slot — it is a data defect (wrong unit, doubled quantity, stale dimension). Route flagged locations to a data-quality queue rather than clamping and forgetting; a run of them usually points at one bad SKU dimension record.