Production scheduling is more than sorting manufacturing orders by priority. Let's build a small Python scheduler that considers machine availability, operation dependencies, and delivery deadlines.
Imagine you're building software for a manufacturing company.
The factory receives customer orders, knows which products need to be manufactured, and has a list of machines available for production.
Your task sounds simple: determine when each manufacturing operation should start and finish.
A first implementation might calculate production time and assign completion dates based on order priority.
But there's a problem.
Two operations cannot use the same machine simultaneously if that machine can process only one operation at a time.
And an assembly operation cannot begin before its required components have completed their previous manufacturing steps.
This is where finite capacity production scheduling becomes interesting from a software engineering perspective.
In this tutorial, we'll build a simplified scheduler using Python and explore the architectural decisions required to turn it into a more realistic manufacturing application.
1. The difference between infinite and finite capacity scheduling
Let's start with a basic example.
A factory has one cutting machine and receives three manufacturing orders:
| Order | Cutting time | Due date |
|---|---|---|
| A | 3 hours | Hour 7 |
| B | 2 hours | Hour 6 |
| C | 4 hours | Hour 9 |
An infinite capacity planning model might place all three orders at the same starting time.
That can be useful for estimating total requirements, but it doesn't represent what will actually happen on a machine capable of processing only one operation at a time.
A finite capacity scheduler must respect the resource constraint.
If order B runs first, for example:
Cutting Machine
Hour: 0 2 5 9
|----|-------|-----------|
B A C
The cutting machine processes B, then A, then C.
No operations overlap.
This is the basic principle behind finite capacity scheduling: production operations must fit within the actual available capacity of their assigned resources.
Of course, real factories also have working calendars, setup times, maintenance windows, and other restrictions.
We'll start with a smaller model.
2. Defining the manufacturing environment
Suppose our factory has two resources:
-
CUTTING: processes raw materials. -
ASSEMBLY: assembles the manufactured components.
Each manufacturing order requires two sequential operations.
First, cutting. Then, assembly.
We will represent the production requirements using Python dictionaries.
jobs = [
{
"id": "A",
"due": 7,
"operations": [
("CUTTING", 3),
("ASSEMBLY", 2),
],
},
{
"id": "B",
"due": 6,
"operations": [
("CUTTING", 2),
("ASSEMBLY", 4),
],
},
{
"id": "C",
"due": 9,
"operations": [
("CUTTING", 4),
("ASSEMBLY", 1),
],
},
]
Each job contains:
- A unique identifier.
- A delivery deadline expressed in elapsed hours.
- An ordered list of manufacturing operations.
- The resource and duration required for each operation.
For this exercise, we'll assume that each resource can process one operation at a time and that all machines are available continuously from hour zero.
We'll also assume that operation durations are known, no breakdowns occur, and operations cannot be interrupted once started.
These assumptions make the example manageable, but they will need to be revisited in a production environment.
3. Choosing a scheduling priority rule
Before assigning operations to machines, we need a rule for selecting manufacturing orders.
There are several possible approaches.
For example:
- First Come, First Served (FCFS)
- Shortest Processing Time (SPT)
- Earliest Due Date (EDD)
- Custom priority scores
Each rule optimizes different aspects of the production process, and none is universally appropriate.
For our implementation, we'll use Earliest Due Date.
This means orders with earlier delivery deadlines receive higher scheduling priority.
In Python:
sorted_jobs = sorted(
jobs,
key=lambda job: job["due"]
)
Our resulting sequence is:
B -> A -> C
This is a dispatching heuristic rather than an optimization algorithm. It does not guarantee the lowest total tardiness or the shortest overall production time.
However, it provides a useful starting point for understanding scheduling logic.
4. Tracking machine availability
Now we need to prevent overlapping operations.
One simple approach is to maintain the next available time for each resource.
machine_available = {
"CUTTING": 0,
"ASSEMBLY": 0,
}
Initially, both machines are available at hour zero.
Whenever an operation is scheduled, the corresponding resource's availability must be updated.
For example, if cutting starts at hour zero and takes two hours:
machine_available["CUTTING"] = 2
The next operation assigned to the cutting machine cannot start before hour two.
But resource availability is only one constraint.
We must also respect the sequence of operations within each manufacturing order.
5. Respecting operation dependencies
Consider order B.
Its routing requires:
CUTTING (2h)
|
v
ASSEMBLY (4h)
Assembly cannot begin until cutting has finished.
Suppose cutting ends at hour two.
Even if the assembly machine was available at hour zero, it must wait until hour two to start processing order B.
The operation start time therefore depends on two conditions:
- When the required machine becomes available.
- When the previous operation of the same order finishes.
We can express this as:
start = max(
machine_available[resource],
job_ready
)
Here, job_ready represents
Top comments (0)