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.
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.