Pizza Factory Simulator
Industrial Engineering and production planning on a seven-station pizza line: due-date estimation, lot handling and recipe adjustment, with live KPIs
A seven-station pizza line simulated on real message brokers: RabbitMQ carries dispatch commands and Kafka records what happens at each station. A scheduler picks the next job by dispatch rule, and a KPI service computes throughput, WIP, cycle time and the bottleneck in real time, compares them with Industrial Engineering theory, and streams everything to a 3D dashboard.
- Role
- Personal project (Requirements Analysis, System Design, Development, Testing)
- Tech
- PythonKafkaRabbitMQFastAPI + SSEThree.jsDockerIndustrial EngineeringProduction Control & Planning
Background
Kafka and message queues both move messages, but from documentation alone the difference between them easily stays abstract: which messages need to be acknowledged as done, and which should be recorded for anyone to replay?
This project gives each one a real job on the same production line, and doubles as practice in Industrial Engineering and production scheduling: the oven is deliberately the slowest station, to see whether the measured numbers match theory.
Key Features
Everything is done from one dashboard, and every change shows up on the line and in the KPIs right away.
- Place orders
- Place orders by hand with a priority, or let a generator place them at a set arrival rate
- Adjust station recipes
- Change each station's recipe per product and watch the bottleneck and KPIs respond
- Live ETA
- Each order's completion time and due date are estimated from bottleneck capacity and queued WIP
- KPI trend charts
- Throughput, WIP and cycle time over time, to compare before and after a change
- Lot handling
- Track each lot's current station and status, and handle rework caused by breakdowns
- Station performance
- Utilization and activity for every station at a glance, with the current bottleneck highlighted
Commands vs Facts
The system follows one rule: RabbitMQ carries commands, Kafka carries facts. A command has one receiver that must report back when done. A fact only states what already happened, so anyone can read it and replay it.
RabbitMQ
Commands (please do this)
- >Orders enter workorders.intake, then the scheduler dispatches jobs to each station's station.<id>.jobs queue
- >Each station uses prefetch 1, handling one job at a time and acking only when done
- >Failures go to a retry queue with a 5-second TTL; jobs over the retry limit or with an invalid format go to a DLQ
Kafka
Facts (this happened)
- >Stations write op_started / op_completed to station.events, partitioned by station
- >Order lifecycle events go to order.lifecycle, and KPI snapshots to a compacted topic
- >The scheduler, KPI service and dashboard each read the same events with their own consumer group
Life of an Order
- 01
Order
A generator places orders at a set arrival rate, or orders are placed by hand on the dashboard, and sent to RabbitMQ.
- 02
Release
The scheduler records a created event, estimates the ETA and due date, and releases orders by the active dispatch rule.
- 03
Dispatch
The scheduler sends the job to the next station's queue. The seven stations are dough, sauce, cheese, toppings, bake, slice and package.
- 04
Process
The station writes op_started, waits the real cycle time for that product, writes op_completed, then acks.
- 05
Hand-off
Stations never talk to each other. The scheduler reads the completion event from Kafka and dispatches the next step.
- 06
Monitor
The KPI service folds events into live metrics and pushes them to the 3D dashboard over SSE.
Reliability
Stations and the scheduler can be interrupted at any moment. The goal is that no work is lost and none is counted twice.
- Kafka first, then ack
- A station acks only after the completion event is flushed to Kafka. A crash causes redelivery, and event_id dedupe records the work once
- Retry and DLQ
- Temporary failures are re-dispatched through a TTL retry queue; invalid messages go straight to the DLQ instead of retrying forever
- Breakdowns and rework
- Stations can break down and be repaired at random. A failed lot waits in a cool-down queue before returning to the scheduler, so it never blocks the station
- Restart recovery
- On restart the scheduler replays events from Kafka to rebuild WIP and lots in cool-down
- Chaos testing
- A script kills containers at random during a run to check that the line recovers by itself
Scheduling and KPIs
Three pizza products take different times at each station, so dispatch rules produce real differences. The oven takes 12 to 14 seconds per pizza while other stations take 3 to 6, making it the bottleneck by design.
- Dispatch rules
- FIFO, EDD and SPT can be switched live, and the same set of orders can be compared across rules
- Priority and due dates
- Orders carry a priority from 1 to 99; at release the ETA is estimated from the bottleneck and queued WIP, and a due date is derived from it
- Live KPIs
- Throughput, WIP, cycle time (mean, p50, p95) and utilization per station
- Bottleneck detection
- Utilization with hysteresis picks the bottleneck station, so it does not flip back and forth between two stations
- Checks against theory
- With warm-up excluded, measurements are checked against Little's Law (L = λW) and bottleneck capacity
Outcome
- >In an overload test, measured throughput reached 91% of the theoretical bottleneck capacity, inside the expected 75% to 115% band.
- >About 1,200 Python tests and 250 frontend tests, with integration tests running against real Kafka and RabbitMQ brokers.
- >Docker starts the brokers, all seven stations and every service with one command, and the same seed reproduces the same orders.