AI-Assisted Production Scheduling
- Official service title: AI-Assisted Scheduling of Production towards Energy Efficiency Increase
- Provider: LMS, Laboratory for Manufacturing Systems and Automation, University of Patras
- Project: EnerTEF, industrial (IND) testing and experimentation facility (TEF)
This page describes an optimization service, not a trained machine learning model. The "AI" (artificial intelligence) in its name means automated planning and search. There are no model weights to download.
The service is a decision-support tool for production planners. It plans and schedules production in a factory in two stages:
- A planning model decides what to make, by which method and on which machine, at the lowest cost.
- A scheduling method then turns that plan into a time-indexed schedule: when each task starts and ends on its machine.
Both stages save energy. When the plan picks machines and methods, it counts both their running cost and the material they waste, and the schedule does not leave machines idle while work is waiting. How the service saves energy explains each point.
This repository holds this page, an example captured from one real run of the service, and a short Python script that summarizes the example.
The optimization service itself is not in this repository and is not covered by its license. It is a hosted service of LMS, University of Patras.
What problem it solves
A factory has orders for products. Most products can be made in more than one way. Each way is called a process plan. A process plan is a chain of jobs, and each job is a set of tasks. A task can often run on one of several machines. Each machine has its own speed, cost per hour and waste rate. Machines only work during their shifts, and raw materials are limited to the stock on hand. Planning all this by hand across several machines easily leads to resource conflicts, idle time and wasted energy.
For a planning window, for example two or three weeks, the service answers three questions:
- Which process plan makes each ordered quantity, and how much material flows through each task?
- Which machine runs each task, and for how long?
- When does each task start and end on its machine?
Stage 1 answers the first two questions at the lowest production cost. Stage 2 answers the third. Together they respect hard limits: machine capacity and stock in stage 1, shift calendars and the order of tasks within each job in stage 2.
In the captured example
One order asks for 3,000 units of product 2 and 2,000 units of product 3. Product 2 has one process plan. Product 3 has two. Three machines are available.
- Stage 1 made all of product 3 through one of its two process plans (plan 4). It used 128.9 machine hours in total on the three machines. It returned
OPTIMAL, with a profit of 911,111.11. - Stage 2 put the five tasks of the two plans in use on the three machines. It kept each job's tasks in order and worked around each machine's non-working periods. The work runs from 1 September 08:00 to 8 September 16:53, in a planning window of 1 to 18 September 2026.
The numbers are demo values. See Example for the files.
How the service saves energy
The energy each machine uses per operation is not yet a separate input (see Limitations). The service still cuts energy use, in four ways.
1. Cheaper machine time (stage 1)
The planning model looks for the plan with the lowest total production cost. Part of that cost is machine time: for every task, the hours on its machine multiplied by that machine's cost per hour. The hours follow from the machine's speed, so a faster machine needs fewer hours for the same work.
If each machine's cost per hour includes its energy cost, the model weighs energy in every choice it makes: which process plan makes each product, and which machine runs each task. A machine that uses more energy then costs more per hour, and the model uses it only where that pays off.
2. Less material waste (stage 1)
Each machine has a waste rate for each task it can run: the share of the task's input that is lost. Lost material has to be made up. The task then needs more input, which means more hours on its machine, more output from every earlier task and more raw material. The model balances material through the whole process plan at once, so it counts all of that extra cost. It weighs that cost against the cost per hour and looks for the lowest total cost. It does not try to cut waste for its own sake: a machine that loses more is used where its lower cost per hour outweighs the material lost. Material that ends up as scrap has already used energy at every step it went through, so losing less of it saves that energy. The model counts that loss only as a cost.
In the captured example, the model made this trade-off in both directions. Task 8 can run on machine 1, which costs 2,000 per hour and loses 10% of its input, or on machine 2, which costs 5,000 per hour and loses nothing. The model chose machine 1 and accepted the loss: the 222 extra units of raw material cost less than the machine time it saved. Task 9 can run on machine 2, which costs 5,000 per hour and loses 10%, or on machine 3, which costs 8,000 per hour and loses nothing. Here the model chose machine 3. A loss at task 9 would have to be made up by task 8 as well, with more machine time there and more raw material, and that would cost more than machine 3's higher rate. The model made nothing through process plan 3, whose tasks 3 and 7 lose 20% and 50% of their input on every machine that can run them.
3. No idle machines while work is waiting (stage 2)
The scheduling heuristic builds the timetable one decision at a time. Each time a machine becomes free, it looks at the tasks that are ready to start. It only considers options in which no free machine is left empty while a ready task it can run is still waiting. A machine left on with nothing to do still uses energy. The only idle gaps the heuristic leaves are those where nothing is ready for the machine, for example while the previous task of the same job is still running on another machine.
In the captured schedule, each task starts as soon as the previous task of its job is done and its machine is free and within working hours. At the start, tasks 1 and 8 are both ready, and both run on machine 1. Machine 1 runs task 8 first, and task 1 starts the moment task 8 is done.
4. Fewer set-ups and fewer interrupted tasks (stage 2)
When the heuristic has a choice between options, it compares them by an estimate of how long they keep the machines occupied. The estimate adds up three times, and the lowest total wins:
- Processing time: when a task can run on more than one machine (possible when you call stage 2 directly), the faster machine scores better.
- Set-up time: the changeover a machine needs between two tasks, read from a set-up matrix in the stage 2 input. A machine being set up uses energy but makes nothing, so the heuristic favors task orders with fewer and shorter changeovers.
- Down time: the part of a machine's non-working periods, from its shift calendar, that falls inside a task. The task stops at the end of one shift and resumes in the next, and the heuristic prefers orders with less of this paused time.
When only one task is ready for a free machine, there is nothing to choose: the heuristic starts it at once, even if a shift end will interrupt it. The example data has no set-up times.
How it works
Stage 1 uses mixed-integer programming (MIP). Stage 2 uses a scheduling heuristic called IMPACT.
orders, products, process plans, machines
|
v
Stage 1: MIP plan -> [service converts the plan] -> Stage 2: IMPACT heuristic -> timed schedule
Stage 1: a plan from a MIP model
MIP is a mathematical method for finding the best plan when some decisions are yes-or-no choices (for example, "does machine 2 run this task?") and others are amounts (for example, "how many units go through this process plan?"). A program called a MIP solver searches for the best plan and can prove that it is the best.
The model looks at the whole planning window at once. It decides how much of each order goes through each process plan, which machine runs each task, and how long each task takes. The material flows, waste and costs follow from those choices. The model has no time axis: a machine's capacity is simply its total working hours in the window. The exact rules are in Objective and constraints.
Between the stages (inside the service)
The service turns the plan into job orders and task orders, and builds the stage 2 input from them:
- It creates one task order for each task of each process plan the plan uses.
- Each task order gets one machine, the one the MIP chose, and the task duration from the MIP (converted to seconds and rounded down).
- Each machine gets its non-working periods for the window, taken from its shift calendar.
- Precedence constraints link tasks of the same job. A precedence constraint says that one task must finish before another can start.
- Every job gets the start of the window as its arrival date and the end of the window as its due date.
This conversion uses data stored inside the service, such as the machines' shift calendars, and the service does not offer it as an endpoint. For that reason the example includes the stage 2 input that the service built itself in the same run.
Stage 2: a timetable from the IMPACT heuristic
A heuristic is a method that finds a good answer quickly by following rules, without proving that the answer is the best possible one. IMPACT is the name of a multi-criteria dispatching heuristic developed at LMS. "Dispatching" means it builds the schedule one decision at a time, each time choosing which waiting task to start next on a free machine. This is combinatorial optimization: at each step it compares alternative task assignments and machine sequences.
At each decision point, IMPACT:
- generates alternative assignments of waiting tasks to free machines, never leaving a free machine empty while a waiting task could run on it;
- for each alternative, simulates a few further decisions along randomly sampled continuations;
- scores each alternative and keeps the best one.
The service scores alternatives with a single, time-based criterion: an estimate of the processing time, set-up time and down time of the sampled continuations, where down time is non-working time that falls inside a task. Lower is better. How the service saves energy explains what this means for energy.
The continuations are random, so two runs on the same input can return different schedules. Both respect the same constraints.
In the service's own pipeline each task arrives with one machine already chosen, so the heuristic decides order and timing, not machine choice. The input format allows several candidate machines per task. If you call stage 2 directly with several, the heuristic also chooses among them.
Example
The example/ folder holds one linked run, captured from the service on demo data, with names replaced by neutral identifiers (ids). The four files are in JSON, a common plain-text format for structured data:
| File | What it is |
|---|---|
stage1_mip_input.json |
The stage 1 input that the service built from its demo orders |
stage1_mip_output.json |
The plan that stage 1 returned in that run |
stage2_heuristic_input.json |
The stage 2 input that the service built from that plan, in the same run |
stage2_heuristic_output.json |
The schedule that stage 2 produced in that run |
Nobody outside the service computed the stage 2 input. The service built it from the stage 1 plan, and that conversion is not exposed. Because the heuristic samples at random, a new run on the same stage 2 input can give a different schedule. example/README.md explains how the files link and what was changed to anonymize them.
Run the example
The offline mode needs Python 3.8 or later and no extra packages:
python run_example.py
It prints a short summary of both stages: the result status, the objective (profit), the machine hours, the number of tasks and assignments, and the first few assignments.
Live access
Live access to the hosted service is available on request through the EnerTEF project or LMS, University of Patras (https://lms.mech.upatras.gr). If the request is approved, you receive an account and instructions for getting an access token.
With a token, the script can send the example to the hosted service. The live mode also needs the requests package. Three optional settings tune the stage 2 search: --dh (decision horizon, DH), --mna (maximum number of alternatives, MNA) and --sr (sampling rate, SR). Heuristic parameters explains them.
python -m pip install requests
export TEF_IND_ACCESS_TOKEN='<your token>' # PowerShell: $env:TEF_IND_ACCESS_TOKEN = '<your token>'
python run_example.py --live # optional: --dh 2 --mna 100 --sr 10
The script sends the stage 1 input and prints the plan summary. Then it sends the stage 2 input and prints the schedule summary. Stage 2 uses the example's stage 2 input, not one built from the new plan, because the conversion runs inside the service. The script reads the token only from the TEF_IND_ACCESS_TOKEN environment variable, never from the command line. Treat the token like a password: do not share it and do not commit it to a repository.
The same calls with curl:
curl -X POST https://api.scheduler.tef-ind.eu/api/scheduling/planFromJSONNODB \
-H "Authorization: Bearer $TEF_IND_ACCESS_TOKEN" -H "Content-Type: application/json" \
--data-binary @example/stage1_mip_input.json
curl -X POST "https://api.scheduler.tef-ind.eu/api/scheduling/scheduleFromJSONNODB?dh=2&mna=100&sr=10" \
-H "Authorization: Bearer $TEF_IND_ACCESS_TOKEN" \
-F "file=@example/stage2_heuristic_input.json;type=application/json"
Limits of the hosted service
The service answers over HTTP (Hypertext Transfer Protocol, the protocol of the web) and reports problems as standard HTTP status codes, such as 403 or 429.
- Your account must be allowed to run optimizations. Accounts that sign in through the EnerTEF federation start read-only. They get HTTP 403 from these endpoints until access is granted.
- The service runs one optimization at a time, across all its users and features. If another one is running, you get HTTP 429 with
Retry-After: 30and the message "Another optimization is already running. Please try again later." Requests are not queued. Wait and try again later. Do not retry in a tight loop. - Keep inputs small. Each input may be at most 2 MB (megabytes).
- A run has no time limit of its own, but the gateway in front of the service gives up after about 60 seconds and answers HTTP 504. The run then continues on the server and keeps the optimization slot, so other requests get 429 until it ends. The example instance finishes well within this limit.
- The two endpoints do not read or change the data stored in the service. The planning component does write each stage 1 input, and the plan when one is found, to its process log on the server. Do not send confidential or personal data.
Limitations
- Energy use is not modeled directly. The models have no energy use per operation, no energy price and no energy forecast. Machine cost per hour is the only cost lever, and energy cost can be included in it.
- Due dates and priorities are not used. Orders carry quantities and prices only. Every job gets the whole planning window as its arrival-to-due interval.
- Precedence between jobs is not passed to stage 2. The stage 2 input links only tasks of the same job. If a process plan has several jobs (for example, one making an intermediate product and one using it), check the schedule for their order.
- In the pipeline each task has one machine. Stage 2 orders and times the MIP's choices; it does not revisit them.
- Stage 2 is randomized. Repeated runs can differ.
- Stage 1 has no time axis. Capacity is the total hours in the window. Whether the work also fits the shift pattern only shows up in stage 2.
- Infeasibility is not explained. An infeasible instance (one with no valid plan) comes back as
INFEASIBLEwith empty results. The service does not say which constraint failed. - Input checks are basic. Missing, unknown and
nullfields get precise messages. Deeper inconsistencies, such as a product with no process plan, get a generic HTTP 400. Some inconsistent inputs returnINFEASIBLE,UNKNOWNor an empty schedule instead of an error. - Runs have no time limit (see Limits of the hosted service).
- The example is tiny. It shows the formats and the method. It is not a benchmark.
Intended use
- Trying the planning and scheduling method on small instances.
- Learning the input and output formats before connecting to the hosted service.
- Experiments within EnerTEF.
Out of scope: production planning decisions without an agreement with LMS, large benchmarks, confidential or personal data, and safety-critical use.
Technical reference
This section is for developers who want to build their own inputs.
Stage 1 input: POST /api/scheduling/planFromJSONNODB
A JSON object (Content-Type: application/json, at most 2 MB). All 26 fields are required and must not be null. Empty lists and objects are allowed. All IDs (identifiers) are strings. Use one quantity unit per product and one currency throughout (the demo data uses metric tons and euros).
| Field | Meaning |
|---|---|
order_ids |
Orders to plan |
product_ids |
Every product involved: ordered products, intermediates and raw materials |
order_products |
Order β products it asks for |
product_quantity |
Order β product β quantity to deliver (must be above zero) |
product_selling_price_per_unit |
Order β product β selling price per unit |
product_cost_per_unit |
Product β cost per unit (what a raw material costs to buy) |
product_stock |
Product β stock at the start of the window |
product_process_plans |
Product β process plans that can make it |
process_plan_jobs |
Process plan β its jobs |
job_tasks |
Job β its tasks |
task_predecessors, task_successors |
Task β tasks right before / after it |
task_material_flow_ratio |
Task β next task β share of the next task's input that comes from this task |
task_consumes, task_consumes_qty |
Task β materials it consumes, and consumption per unit of task input |
task_produces, task_produces_qty |
Task β products it makes, and production per unit of task output |
job_consumes, job_produces |
Job β materials it consumes / products it makes |
job_successors |
Job β later job β products passed between them |
machine_ids |
Machines |
machine_capacity |
Machine β working hours available in the window (must be above zero) |
machine_cost_per_hour |
Machine β cost per working hour |
task_machines |
Task β machines that can run it |
machine_nominal_rate |
Machine β task β units of task input processed per hour |
machine_waste_rate |
Machine β task β fraction of task input lost as waste |
Every pair in task_machines needs a nominal rate and a waste rate, and every product in product_ids needs a stock and a cost. If one is missing, the solve fails and the service answers HTTP 400 with "The planning service could not solve this instance."
The smallest valid instance has one order, one task and one machine. The service returns OPTIMAL with objective 490 for it:
{"order_ids":["1"],"product_ids":["10","20"],"order_products":{"1":["10"]},"machine_ids":["100"],
"machine_capacity":{"100":40.0},"machine_cost_per_hour":{"100":50.0},"product_stock":{"10":0.0,"20":1000.0},
"product_cost_per_unit":{"10":5.0,"20":1.0},"product_selling_price_per_unit":{"1":{"10":100.0}},
"product_quantity":{"1":{"10":10.0}},"product_process_plans":{"10":["1000"]},"process_plan_jobs":{"1000":["2000"]},
"job_tasks":{"2000":["3000"]},"task_machines":{"3000":["100"]},"task_consumes":{"3000":["20"]},
"job_consumes":{"2000":["20"]},"task_produces":{"3000":["10"]},"job_produces":{"2000":["10"]},
"task_predecessors":{},"task_successors":{},"job_successors":{"2000":{}},
"task_consumes_qty":{"3000":{"20":1.0}},"task_produces_qty":{"3000":{"10":1.0}},"task_material_flow_ratio":{},
"machine_waste_rate":{"100":{"3000":0.0}},"machine_nominal_rate":{"100":{"3000":1.0}}}
Stage 1 output
feasible is OPTIMAL, INFEASIBLE, OTHER or UNKNOWN. objective_value is the profit. It is 0 when the status is not OPTIMAL. An infeasible instance still returns HTTP 200, with empty result maps.
There are 17 result maps. Apart from machine_usage, they are nested by order β product β process plan β job β task, as deep as each one needs:
| Field | Keyed by | Content |
|---|---|---|
process_plan_production |
order, product, plan | Quantity made through each process plan |
task_assignment |
..., task, machine | true for the machine chosen |
task_duration |
..., task | Duration in hours |
task_total_consumption, task_total_production, task_waste |
..., task | Quantity into, out of, and lost in the task |
task_consumes, task_produces |
..., task, product | Quantity of each material or product |
tasks_flow |
..., task, next task | Quantity passed on |
job_consumes, job_produces |
..., job, product | Quantity of each material or product |
task_total_cost, job_total_cost, process_plan_total_cost |
..., task / job / plan | Cost |
process_plan_cost_per_unit |
order, product, plan | Cost per unit made |
product_cost_per_unit |
order, product | Cost per unit delivered |
machine_usage |
machine | Hours used |
Every process plan of an ordered product appears. Plans the solution does not use show zero quantities.
Objective and constraints (stage 1)
The objective is what the model tries to maximize. Here it is profit: for every ordered item, quantity Γ (selling price β production cost per unit). The ordered quantities are fixed, so in practice the model finds the lowest total production cost: machine hours Γ cost per hour, plus purchased material Γ cost per unit.
The model decides how much of each ordered quantity goes through each process plan; one machine per task; each task's input, output, waste and duration; the flows between tasks; and each machine's working hours.
The constraints, in plain words:
- Demand: for each order and product, the quantities made through its process plans add up exactly to the ordered quantity.
- One machine per task: each task runs on exactly one of its eligible machines.
- Duration: task input = duration Γ the chosen machine's nominal rate.
- Capacity: the hours used on each machine stay within its available hours in the window.
- Waste: waste = the chosen machine's waste rate Γ task input. Task output = input β waste.
- Material balance within a job: task input = consumed materials + flow from preceding tasks. Task output = flow to following tasks + products made. Consumption, production and flows follow the given ratios.
- Between jobs: what one job passes on equals what the next job takes in.
- Stock: total consumption of each material stays within what the plan produces of it plus the starting stock.
- Costs: task cost = duration Γ machine cost per hour + consumed materials Γ their cost. Job and plan costs add up. Cost per unit = plan cost Γ· quantity.
Some constraints multiply two decisions together (for example, duration Γ machine choice). This makes the model non-convex, a harder class of problem than a plain linear model. A MIP solver solves it. OPTIMAL means the solver proved that the plan is optimal. There is no time limit.
Stage 2 input: POST /api/scheduling/scheduleFromJSONNODB
multipart/form-data with a part named file that holds the stage 2 input as JSON (at most 2 MB). The optional integer parameters dh, mna and sr go in the query string or as form fields (see Heuristic parameters).
The stage 2 input is the service's internal format. The easiest start is example/stage2_heuristic_input.json: keep its structure and change the values. Unknown keys are rejected.
| Key | Content |
|---|---|
planStartDateYear, planStartDateMonth, planStartDateDay (and planEndDate...) |
Planning window, as strings |
continueAssignmentsAfterPlanEndDate |
"false" in the service's pipeline: tasks may not run past the window |
workcenters.workcenter[] |
One workcenter with algorithm "MULTICRITERIA" and a workcenterresourcereference[].refid per resource |
resources.resource[] |
id, name, non-working periods under resourceavailability.nonworkingperiods.period[] (fromdate, todate), optional setupmatrixreference |
jobs.job[] |
id, name, arrivaldate, duedate, jobworkcenterreference.refid, jobtaskreference[].refid |
tasks.task[] |
id, name |
tasksuitableresources.tasksuitableresource[] |
taskreference.refid, resourcereference.refid, operationtimeperbatchinseconds, setupcode |
taskprecedenceconstraints.taskprecedenceconstraint[] |
preconditiontaskreference.refid finishes before postconditiontaskreference.refid starts |
setupmatrices.setupmatrix[] |
id and setup[] entries with fromcode, tocode, timeinseconds |
A resource's setupmatrixreference.refid must equal the id of a set-up matrix, or its set-up times are ignored.
Stage 2 output
{
"message": "Planning simulation executed successfully from uploaded JSON file.",
"dh": 2, "mna": 100, "sr": 10,
"assignmentCount": 3,
"schedule": {
"id": "planning output",
"assignments": {"assignment": [
{"task": {"id": "_57"}, "resource": {"id": "_2"},
"timeofdispatch": {"year": 2026, "month": 9, "day": 1, "hour": 8, "minutes": 0, "seconds": 0},
"durationinmilliseconds": 16333000}
]}
}
}
The values above only show the shape. Each assignment gives the task, its resource (machine), its start time (timeofdispatch) and its duration. Task and resource IDs are exactly the ones in the input. Times are plain clock times in the same frame as the input dates; no time zone is attached. If assignmentCount is lower than the number of tasks, some tasks did not fit in the window.
Heuristic parameters (stage 2)
| Parameter | Name | What it controls | Default | Allowed |
|---|---|---|---|---|
dh |
Decision horizon | How many further decisions are simulated for each alternative | 2 | 1 to 5 |
mna |
Maximum number of alternatives | How many alternative assignments are generated at each decision point | 100 | 1 to 1000 |
sr |
Sampling rate | How many random continuations are simulated per alternative | 10 | 1 to 100 |
Higher values search more and take longer. Run time grows roughly in proportion to mna, and much faster with dh. The service's own pipeline always uses 2 / 100 / 10. The stage 2 endpoint lets you choose. Values outside the allowed range get HTTP 400.
License
The text of this page, the example data and run_example.py are licensed under CC BY 4.0 (Creative Commons Attribution 4.0 International).
The optimization service (the planning model, the scheduling heuristic and their hosting) is not included in this repository and is not covered by this license. It is a hosted service of LMS, University of Patras.
Acknowledgement
Funded by the European Union. This work is part of the EnerTEF project, which has received funding from the European Union's Horizon Europe research and innovation program under grant agreement No 101172887 (project page on CORDIS, the European Union's research and development information service). Views and opinions expressed are however those of the author(s) only and do not necessarily reflect those of the European Union or the European Research Executive Agency (REA). Neither the European Union nor REA can be held responsible for them.
Citation and contact
@misc{enertef_ai_assisted_production_scheduling_2026,
title = {AI-Assisted Production Scheduling},
author = {{LMS, Laboratory for Manufacturing Systems and Automation, University of Patras}},
year = {2026},
howpublished = {\url{https://huggingface.co/EnerTEF/AI-Assisted-Production-Scheduling}},
note = {EnerTEF project}
}
Contact and live access: on request through the EnerTEF project or LMS, University of Patras (https://lms.mech.upatras.gr).