Load Building Outbound multi-temp loads, proven legal on six hard constraints before a trailer leaves the dock

What this demo proves

Outbound load building does not have a prediction problem. By the time a trailer is staged, the orders are picked and counted to the case. The open question is not how much will ship, it is whether the way we packed and routed it is actually legal. This demo answers that question with a proof rather than a hope: every trailer load is checked against six independent hard constraints, and no load is allowed to depart until it passes all six. The route optimizer proposes consolidations; the constraint engine is the authority that accepts or rejects them. The optimizer is checked, never trusted.

The contrast that makes the point is a legacy cube-only load builder: it merges stops whenever the total volume fits inside the trailer and ignores everything else. Run side by side on the same orders, it ships loads that overrun the frozen compartment, exceed the axle weight limit, crush fragile product, or miss delivery windows. The verified builder refuses those same merges and logs exactly why.

How it works

The pipeline has four stages, all deterministic from a seed:

  1. Known orders in. generate_orders(network, scenario, seed) produces per-store order lines with exact case counts. This is post-pick reality, not a forecast.
  2. Clarke-Wright savings merges. The builder starts with one load per store, then walks candidate merges in descending order of routing savings (the classic depot-distance savings heuristic, adapted so the unit being merged is a store stop on a multi-temperature trailer). A store whose own order overruns a compartment is first split by temperature class into feasible pieces.
  3. Every merge checked by the constraint engine. A merge is accepted only if LoadConstraintEngine.check_load returns feasible on the combined load. This is the crucial difference from ordinary savings routing: the savings heuristic proposes, the six-rule engine disposes.
  4. Rejected merges logged, certificates issued. Every refused merge is recorded with the single rule that blocked it and the measured numbers. Every accepted load is issued a certificate: the full enumeration of all six rules with their measured and limit values, plus a hash of the load.
A rejected merge costs efficiency and buys safety. When the engine refuses to combine two stores onto one trailer, the plan uses an extra trailer and a few more miles. That is the price of legality, made explicit. The demo shows both sides of the trade: the verified plan uses more trailers than the cube-only plan, and in exchange it ships zero illegal loads instead of the cube-only builder's several.

The six rules

Each rule is a small, independent, named check in grocery_demos/core/constraints.py. The engine always returns all six, pass or fail, each with a human-readable detail string and the measured value against its limit. The example detail strings below are real output from a seed-42 run.

RuleWhat it enforcesExample detail (seed 42)
temp_zone_integrity Every line rides in the compartment matching its temperature class, and each compartment stays within its cube and weight capacity (frozen 550 ft³ / 9,000 lb, refrigerated 900 ft³ / 14,000 lb, ambient 1,500 ft³ / 22,000 lb). frozen: 855/550 ft3 OVER; frozen: 10841/9000 lb OVER
payload_weight Total case weight across all compartments stays within the trailer payload limit of 43,000 lb. total 49625/43000 lb OVER
stacking_safety Fragile cases (crush class 3: bread, eggs, chips, produce) need top-tier headroom. Each compartment must fit its normal cube plus a 30% surcharge on fragile cube. ambient: 535 fragile cases need 190 ft3 top-tier; 110 ft3 free
delivery_windows Simulate the route from the DC at 06:00, driving at 45 mph and unloading 30 to 45 minutes per stop; every arrival must fall inside the store's delivery window. Early arrivals wait. Store 117: arrive 4.63h after close 4.0h
route_duration_hos Drive time plus unload plus wait plus the return leg to the DC must fit the 10-hour hours-of-service limit. Guards long multi-stop routes. route 4.65/10.0 h within (passing; the limit binds on longer routes)
unload_accessibility Last-in-first-out pallet access: at most three stops per trailer, and a tight-dock store may not sit behind other stops (it must be delivered first). Store 115 is tight-dock but stop 2, not first

Scenarios and their seed-42 results

Four order scenarios stress different failure modes. The numbers below are the actual output of running the pipeline at seed 42. "Verified trailers" is the load count the verified builder produces; "avg cube utilization" is filled volume over total trailer volume averaged across those trailers; "naive violating loads" is how many of the cube-only builder's loads fail at least one rule.

Scenario Verified trailers Avg cube util Verified miles Naive violating loads Rules the naive loads break
weekday
baseline volume
19 74.6% 113.6 7 unload_accessibility (7), delivery_windows (3)
weekend_peak
produce and dairy heavy
34 50.9% 179.5 14 stacking_safety (14), temp_zone_integrity (11), unload_accessibility (5), delivery_windows (2)
holiday_surge
some stores exceed one trailer
43 53.9% 290.3 12 temp_zone_integrity (12), stacking_safety (12), payload_weight (5)
frozen_promo
frozen lines tripled
36 34.1% 188.6 16 temp_zone_integrity (16), stacking_safety (16), unload_accessibility (7), delivery_windows (3)

Counts per rule add to more than the violating-load total because a single illegal load usually breaks several rules at once. In every scenario the verified builder produces a plan that is fully feasible and covers every order line exactly, while the cube-only builder ships between 7 and 16 illegal loads. frozen_promo is the sharpest illustration: tripling frozen volume makes cube-only merges overrun the 550 ft³ frozen compartment specifically, so 16 of the 17 cube-only loads are illegal.

Reading the UI

The live page at /grocery/loadbuild has six tabs:

Reading the rejected-merge log

Each entry names the stores the builder tried to combine, the single rule that blocked the merge, and the measured numbers, for example: merge of Store 116 + Store 123 rejected -- temp_zone_integrity: frozen 855/550 ft3 OVER. This log is the demo's central artifact. It is the running record of the optimizer being overruled, and it turns "the system is safe" into a list of specific consolidations that were refused and the exact limit each one would have broken. At seed 42 the four scenarios log between 78 and 908 rejected merges each; the UI shows a readable sample and reports the full count.

The certificate

Every accepted load is issued a certificate by grocery_demos/core/certificates.py. It contains:

Why enumerate every rule, not just the failures. A certificate that listed only what failed would be a bug report. A certificate that lists all six rules with their measured margins is an audit record: it shows not just that the load is legal, but how much headroom it had on each constraint. That is what lets an auditor, a carrier, or a receiving manager trust the plan without rerunning it, and what lets an investigator reconstruct exactly what was checked if something goes wrong downstream.

Honest caveats

What is real and what is staged.

Related

Try it live

Run the four scenarios, watch merges get rejected, and read the certificates.

Suite overview

How load building fits with the inbound and execution demos, and the shared synthetic world they run on.

Sibling demos

Replenishment adds forecast guardrails; re-planning re-verifies after a mid-day disruption.