Slotting Architecture · 11 min read

How to Model Golden-Zone Ergonomic Limits

Your velocity ranking says SKU 40817 is an A-mover picked 240 times a shift, so the optimizer drops it into the first open bin in the forward zone — which happens to be a top shelf at 78 inches. Now every pick is an overhead reach on a 14 kg case, and by the second hour your picker is on a step ladder for a fast-mover. The fix is to teach the slotting engine what a human body can comfortably reach: the ergonomic golden zone, the band of rack height between roughly knee and shoulder where lifting force is highest and spinal load is lowest. This page shows you how to define that band as a numeric constraint and score any candidate slot’s ergonomic suitability, so high-velocity and heavy SKUs are steered into low-strain positions instead of landing wherever capacity happens to open up. It is the ergonomic-suitability case of the Weight & Volume Constraint Modeling cluster inside the wider Location Assignment & ABC Classification Algorithms system.

The golden zone is not a preference you bolt on after assignment — it is a soft constraint the assignment objective should price. The A-tier placements proposed by ABC Classification Tuning earn the golden band precisely because they are picked most often, so the strain saving compounds across the shift. Weight interacts with height: a band that is comfortable for a 3 kg carton is a shoulder injury waiting to happen for an 18 kg one, so the model must score the pair of slot height and SKU mass, never height alone.

Rack elevation split into floor, golden, and high ergonomic bands with reach and lift limits A rack drawn in elevation with a height scale in inches running from 0 at the floor to 84 at the top. Three horizontal bands are shaded. The floor band from 0 to 30 inches requires a stoop or squat, loads the lower back, and accepts heavy cases up to the shelf crush limit; it is reserved for slow-and-heavy SKUs. The golden band from 30 to 60 inches spans knee to shoulder height, keeps lifting force high and spinal load low, caps a single-hand lift near 18 kilograms, and is where A-movers earn placement. The high band above 60 inches is an overhead reach that triggers step-ladder events, so it caps lift near 5 kilograms and holds only slow movers. Each band connects by an arrow to a callout describing its reach posture and weight ceiling. Ergonomic reach bands on a rack elevation 84 in 60 in 30 in 0 in HIGH GOLDEN ZONE FLOOR High · above 60 in (overhead reach) step-ladder events · max lift 5 kg slow movers only — never a heavy A-mover Golden · 30–60 in (knee to shoulder) high lift force, low spinal load · max lift 18 kg A-movers earn this band Floor · below 30 in (stoop / squat) loads lower back · heavy OK to crush limit slow-and-heavy SKUs
Golden-zone reach bands. The 30–60 in band minimizes strain and holds the fastest movers; height and weight are scored together, not separately.

Prerequisites

Confirm each of these before wiring the scorer into an assignment run:

  • Python 3.10+ — the implementation uses X | None unions, float type hints, and a frozen dataclass for the config.
  • A slot height field — every candidate location must expose the pick-face center height in inches (or a unit you normalize on ingestion). Deriving it from the Location Hierarchy Mapping topology once, up front, keeps the scorer in a single unit system.
  • Normalized SKU gross weight in kilograms — the same gross mass the Weight & Volume Constraint Modeling cluster derates for beam limits; ergonomics scores the human lift, not the rack rating.
  • A velocity tier per SKU — so the scorer can prioritize golden-band slots for A-movers. The tier comes from ABC Classification Tuning.
  • A per-band lift table — the maximum single-person lift you will allow at floor, golden, and high bands, ideally traced to your facility’s manual-handling assessment rather than a guessed default.

Configuration Block

Every tunable lives in one externalized profile. The two levers that decide behavior are the golden-band boundaries (golden_min_in / golden_max_in) and the per-band lift ceilings (max_lift_kg_by_band), which together encode “how high is comfortable” and “how heavy is safe at that height”.

# ergonomics.yaml — golden-zone tunables, one profile per facility
ergonomics:
  golden_min_in: 30.0          # bottom of comfortable reach (approx. knee)
  golden_max_in: 60.0          # top of comfortable reach (approx. shoulder)
  floor_max_in: 30.0           # at or below this is a stoop/squat pick
  high_min_in: 60.0            # above this is an overhead reach (ladder risk)
  max_lift_kg_by_band:
    floor: 23.0                # squat lift; back-loaded but strong
    golden: 18.0              # single-hand comfortable ceiling in the sweet spot
    high: 5.0                  # overhead; keep light or it becomes a two-person job
  golden_bonus: 1.0            # score awarded to a golden placement
  overweight_penalty: 0.6      # subtracted when SKU mass exceeds the band ceiling
# Equivalent Python config dict consumed by ErgonomicConfig
ERGONOMICS = {
    "golden_min_in": 30.0,
    "golden_max_in": 60.0,
    "floor_max_in": 30.0,
    "high_min_in": 60.0,
    "max_lift_kg_by_band": {"floor": 23.0, "golden": 18.0, "high": 5.0},
    "golden_bonus": 1.0,
    "overweight_penalty": 0.6,
}

Implementation

The scorer answers two questions for a candidate (slot_height_in, sku_weight_kg) pair: which band does the slot fall in, and how ergonomically suitable is placing this mass there. is_golden_zone is a cheap pass/fail on the height boundary; ergonomic_score folds in the weight interaction so a golden slot that would overload the picker no longer scores like a free win. The score is a soft signal in the range roughly 0.01.0 that the assignment objective adds as a reward (or, negated, a penalty).

from __future__ import annotations

import logging
from dataclasses import dataclass

logger = logging.getLogger("slotting.ergonomics")


@dataclass(frozen=True)
class ErgonomicConfig:
    """Facility-specific golden-zone tunables (heights in inches, lifts in kg)."""
    golden_min_in: float = 30.0
    golden_max_in: float = 60.0
    floor_max_in: float = 30.0
    high_min_in: float = 60.0
    max_lift_kg_by_band: dict[str, float] = None  # set in __post_init__ default
    golden_bonus: float = 1.0
    overweight_penalty: float = 0.6

    def __post_init__(self) -> None:
        if self.max_lift_kg_by_band is None:
            object.__setattr__(
                self, "max_lift_kg_by_band",
                {"floor": 23.0, "golden": 18.0, "high": 5.0},
            )


def band_for_height(height_in: float, cfg: ErgonomicConfig) -> str:
    """Classify a slot's pick-face center height into floor / golden / high."""
    if height_in <= cfg.floor_max_in:
        return "floor"
    if height_in >= cfg.high_min_in:
        return "high"
    return "golden"


def is_golden_zone(height_in: float, cfg: ErgonomicConfig) -> bool:
    """True when the slot sits in the comfortable knee-to-shoulder band."""
    return cfg.golden_min_in <= height_in <= cfg.golden_max_in


def ergonomic_score(
    height_in: float, sku_weight_kg: float, cfg: ErgonomicConfig
) -> float:
    """Score ergonomic suitability of placing a mass at a slot height (0.0-1.0).

    Golden placements earn golden_bonus. Any band charges an overweight_penalty,
    scaled by how far the SKU exceeds that band's lift ceiling, so a heavy SKU in
    the golden zone still outscores the same SKU overhead, but never scores free.
    """
    band = band_for_height(height_in, cfg)
    ceiling = cfg.max_lift_kg_by_band[band]
    base = cfg.golden_bonus if band == "golden" else 0.4 if band == "floor" else 0.1

    over = max(0.0, sku_weight_kg - ceiling) / ceiling
    penalty = cfg.overweight_penalty * min(over, 1.0)
    score = max(0.0, base - penalty)

    if over > 0.0:
        logger.warning(
            "SKU %.1fkg exceeds %s ceiling %.1fkg at %.0fin (score=%.2f)",
            sku_weight_kg, band, ceiling, height_in, score,
        )
    else:
        logger.info(
            "slot %.0fin band=%s weight=%.1fkg score=%.2f",
            height_in, band, sku_weight_kg, score,
        )
    return round(score, 3)

Step-by-Step Walkthrough

  1. Classify the height first. band_for_height maps a slot’s pick-face center height to one of three bands using floor_max_in and high_min_in. Everything else keys off the band, so a wrong height here poisons every downstream comparison — normalize the unit at ingestion, not at score time.
  2. Keep the golden check separate and cheap. is_golden_zone is a pure boolean on golden_min_in/golden_max_in. The assignment layer uses it for fast pre-filtering (“only golden slots for the top 5% of velocity”) before it ever calls the heavier scorer.
  3. Look up the band’s lift ceiling. ergonomic_score reads max_lift_kg_by_band[band]. This is where the weight-height interaction lives: the same 20 kg case is under the 23 kg floor ceiling but well over the 5 kg high ceiling, so it scores very differently by band.
  4. Charge a proportional overweight penalty. Rather than a hard cliff, the score subtracts overweight_penalty scaled by the fractional overshoot (weight − ceiling) / ceiling, capped at one ceiling’s worth. A SKU 10% over its band loses a little; one at double the ceiling loses the full penalty and can floor at 0.0.
  5. Emit a log line either way. A clean placement logs at INFO; an over-ceiling placement logs at WARNING so a shadow run surfaces every ergonomic compromise the optimizer was willing to make. Feed the returned score into the objective as a reward term next to travel cost.

Verification

Assert the invariants directly — a light SKU in the golden band scores highest, the same heavy SKU scores worse overhead than on the floor, and the golden boolean respects its boundaries. These checks run without a WMS.

import logging

logging.basicConfig(level=logging.INFO)
cfg = ErgonomicConfig()

# A light case in the sweet spot is the best possible placement.
assert ergonomic_score(48.0, 4.0, cfg) == 1.0

# The golden boolean respects both boundaries (inclusive).
assert is_golden_zone(30.0, cfg) and is_golden_zone(60.0, cfg)
assert not is_golden_zone(72.0, cfg)

# A 20 kg case is fine on the floor but punished overhead.
floor = ergonomic_score(18.0, 20.0, cfg)   # under 23 kg floor ceiling
high = ergonomic_score(72.0, 20.0, cfg)    # 4x the 5 kg high ceiling
assert floor > high
print(f"OK - floor={floor} beats overhead={high}")

Sample expected output:

INFO:slotting.ergonomics:slot 48in band=golden weight=4.0kg score=1.00
INFO:slotting.ergonomics:slot 18in band=floor weight=20.0kg score=0.40
WARNING:slotting.ergonomics:SKU 20.0kg exceeds high ceiling 5.0kg at 72in (score=0.00)
OK - floor=0.4 beats overhead=0.0

Common Pitfalls

  • Scoring height without weight. A pure “is it golden?” flag rewards putting an 18 kg case at shoulder height as much as a 2 kg one. The whole point of ergonomic_score is the weight interaction — if you drop the per-band ceiling, you have not modeled ergonomics, only reach. Always score the height/weight pair.
  • One static config for every picker. A band tuned to a 50th-percentile male reach strains shorter pickers and wastes the top inches for taller ones. Where you assign SKUs to fixed teams, key golden_min_in/golden_max_in and the lift ceilings to that population’s manual-handling assessment rather than a single facility default.
  • Ignoring top-shelf ladder events. A slow mover overhead is fine; a frequent one is a repeated ladder climb that no beam rating flags. Combine this score with velocity — never let an A-tier SKU from ABC Classification Tuning settle above high_min_in, regardless of how light it is.
  • Treating the score as a hard gate. Ergonomics is a soft constraint: in a saturated forward zone the solver may have to accept a mediocre band. Feed the score as a priced reward, not a reject, or you will strand fast-movers in distant aisles chasing a golden slot that no longer exists. Heavy cases that genuinely cannot go up belong in the ground-level flow covered by How to Assign Heavy SKUs to Ground-Level Slots.