Home
/2
Good. This is the right abstraction. The inventory simulator should not know where demand comes from. It should only receive tasks and process them.
The structure becomes:
inventory_sim/
│
├── materials.csv # Master data
├── demand_generator.py # Creates tasks
├── simulation.py # Inventory logic only
├── models.py # Data objects
└── main.py # Runs simulation
The flow is now:
Demand Generator
|
v
Task / Auftrag
|
v
Inventory Simulation
|
+--> Consumption CSV
|
+--> Purchase CSV
|
+--> Inventory Snapshot
---
models.py
Add a proper task model.
from dataclasses import dataclass
@dataclass
class Material:
material_id: str
description: str
stock: int
unit_price: float
min_stock: int
max_stock: int
@dataclass
class TaskLine:
material_id: str
quantity: int
@dataclass
class Task:
task_id: str
day: int
lines: list[TaskLine]
---
demand_generator.py
This is now responsible only for creating demand.
Later this can be replaced with SAP data.
import random
from models import Task, TaskLine
class DemandGenerator:
def __init__(self, material_ids):
self.material_ids = material_ids
self.task_counter = 0
def generate_day(self, day):
tasks = []
number_of_tasks = random.randint(0, 5)
for _ in range(number_of_tasks):
self.task_counter += 1
lines = []
number_of_materials = random.randint(1, 4)
selected = random.sample(
self.material_ids,
number_of_materials
)
for material_id in selected:
lines.append(
TaskLine(
material_id,
random.randint(1, 8)
)
)
tasks.append(
Task(
task_id=f"TASK-{self.task_counter:06}",
day=day,
lines=lines
)
)
return tasks
Example generated demand:
Day 15
TASK-000231
M001 Bearing x3
M004 Shaft x1
TASK-000232
M002 Bolt x15
M003 Nut x8
---
simulation.py
Remove all random generation.
The simulator only receives tasks.
Replace the old task generation part with:
def process_day(self, day, tasks):
self.day = day
for task in tasks:
self.process_task(task)
And modify process_task:
def process_task(self, task):
for line in task.lines:
material = self.materials[line.material_id]
consumed = min(
line.quantity,
material.stock
)
material.stock -= consumed
self.consumption_history.append(
{
"day": task.day,
"task_id": task.task_id,
"material": material.material_id,
"quantity": consumed
}
)
self.check_replenishment(material)
---
Add a separate purchasing function:
def check_replenishment(self, material):
if material.stock <= material.min_stock:
quantity = (
material.max_stock
- material.stock
)
cost = quantity * material.unit_price
if self.money >= cost:
self.money -= cost
material.stock += quantity
self.purchase_history.append(
{
"day": self.day,
"material": material.material_id,
"quantity": quantity,
"cost": cost
}
)
Now purchasing is isolated.
---
main.py
Now the orchestration is very clear.
from models import Material
from simulation import InventorySimulation
from demand_generator import DemandGenerator
import csv
def load_materials(filename):
materials=[]
with open(filename) as f:
reader=csv.DictReader(f)
for row in reader:
materials.append(
Material(
row["material_id"],
row["description"],
int(row["stock"]),
float(row["unit_price"]),
int(row["min_stock"]),
int(row["max_stock"])
)
)
return materials
materials = load_materials(
"materials.csv"
)
simulation = InventorySimulation(
materials,
starting_money=50000
)
demand = DemandGenerator(
[
m.material_id
for m in materials
]
)
simulation_days = 365
for day in range(1, simulation_days + 1):
tasks = demand.generate_day(day)
simulation.process_day(
day,
tasks
)
simulation.export_csv()
---
Now the CSV outputs become more realistic
consumption_history.csv
day,task_id,material,quantity
15,TASK-000231,M001,3
15,TASK-000231,M004,1
15,TASK-000232,M002,15
This represents your "System Change" / consumption table.
---
purchase_history.csv
day,material,quantity,cost
15,M002,45,90
23,M001,33,495
---
Future replacement with real data
The nice thing now:
Today:
DemandGenerator
|
|
random tasks
Later:
DemandGenerator
|
|
SAP CSV import
|
|
real Auftrag numbers
The simulator does not change.
Your future SAP export would just need something like:
task_id,day,material_id,quantity
4711001,1,M001,4
4711001,1,M002,10
4711002,1,M003,2
and you replace only demand_generator.py.
This is now much closer to a real spare-parts inventory digital twin architecture.