A full week is full

· 2 min · planning · pyspark

Every scheduling tutorial I read while building our planning engine had the same shape: distribute demand, and where a period runs over capacity, spread the excess into neighbouring periods until things look level. It produces beautiful charts. It is also a lie, and the planners spotted it in about a minute.

The problem is what "over capacity" means on a real shop floor. If a cell can produce a certain number of minutes of work in a week, it cannot produce more because a spreadsheet asked nicely. Any schedule that places work above that line is not a plan; it is a wish with a due date attached.

The rule

What we settled on was blunt enough to fit in one sentence:

If it is late, it is late. We cannot over-cap.

Mechanically that means a week which is full is simply full. Work does not get squeezed in, and it does not go into a virtual "overflow" lane that quietly absorbs anything awkward. It cascades to the next week that genuinely has room. If that lands after the shipment date, the order is late, and the plan says so, in the open, where someone can do something about it.

This is less comfortable than levelling. It produces schedules with visible lateness in them. That turned out to be the point: a plan that admits it cannot deliver 300 pieces is actionable, and a plan that hides them inside an over-capacity week is not.

Levelled · the tutorial smooth, and partly impossible cell ceiling 3 weeks planned above what the cell can make Never over-cap · what shipped honest, and actionable cell ceiling 540 pcs → overtime / outsourcing On time Late, but makeable Above the ceiling · cannot be made Levelled — the tutorial smooth, and partly impossible cell ceiling 3 weeks above what the cell can make Never over-cap — what shipped honest, and actionable cell ceiling 540 pcs → overtime / outsourcing On time Late Cannot be made
Same order book, two plans. Levelling hides the shortfall inside weeks that cannot be produced; capping exposes it as lateness plus a purchasing decision.

What cannot fit does not vanish

The corollary matters as much as the rule. If capacity is genuinely bounded and demand exceeds it, some quantity has nowhere to go. The temptation is to drop it: push it past the horizon, let it fall off the end of the array, and report a tidy number.

Instead it comes out as a named figure: the overtime and outsourcing requirement for that week. It is the most useful number the engine produces, because it converts an impossible schedule into a purchasing decision.

The gate you are watching is usually the wrong one

The other correction came from watching which constraint actually binds.

There is always an obvious headline capacity: a plant-wide figure, pieces per week, the number that goes in the board pack. It is almost never the one that decides the schedule. In our case the binding constraint was the minutes available in a particular finishing cell, and no amount of raising the plant-wide number moved the outcome at all.

So every blocked quantity records which gate stopped it. Not "this week is full" but "this week is full because of cell minutes, and here is how many pieces that cost you". Several gates apply at once (a plant total, per-cell minutes, per-family sub-caps) and an order can be blocked by more than one simultaneously.

Once the report could say which ceiling was the real one, the conversation stopped being about the schedule and started being about capacity. Which is where it should have been all along.