DEV Community

Nomus Industrial ERP
Nomus Industrial ERP

Posted on

Building a Simple Finite Capacity Production Scheduler with Python

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
Enter fullscreen mode Exit fullscreen mode

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),
        ],
    },
]
Enter fullscreen mode Exit fullscreen mode

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"]
)
Enter fullscreen mode Exit fullscreen mode

Our resulting sequence is:

B -> A -> C
Enter fullscreen mode Exit fullscreen mode

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,
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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:

  1. When the required machine becomes available.
  2. When the previous operation of the same order finishes.

We can express this as:

start = max(
    machine_available[resource],
    job_ready
)
Enter fullscreen mode Exit fullscreen mode

Here, job_ready represents

Top comments (0)