diff --git a/optis/backend/__pycache__/agent.cpython-311.pyc b/optis/backend/__pycache__/agent.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..60b04fbb5f7da1a9b02b7cb1c95d6165a64c8e19
Binary files /dev/null and b/optis/backend/__pycache__/agent.cpython-311.pyc differ
diff --git a/optis/backend/__pycache__/environment.cpython-311.pyc b/optis/backend/__pycache__/environment.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e5dc19df2ec923f892d4833c9d5a8517fa0ebbc7
Binary files /dev/null and b/optis/backend/__pycache__/environment.cpython-311.pyc differ
diff --git a/optis/backend/__pycache__/eventlog.cpython-311.pyc b/optis/backend/__pycache__/eventlog.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d96682b9202d2f07c88f3b7ea777fc80cf74aabc
Binary files /dev/null and b/optis/backend/__pycache__/eventlog.cpython-311.pyc differ
diff --git a/optis/backend/__pycache__/simplesimmodel.cpython-311.pyc b/optis/backend/__pycache__/simplesimmodel.cpython-311.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2ae869e5646e548e0a24351de27367837deba250
Binary files /dev/null and b/optis/backend/__pycache__/simplesimmodel.cpython-311.pyc differ
diff --git a/optis/backend/agent.py b/optis/backend/agent.py
new file mode 100644
index 0000000000000000000000000000000000000000..36e347a00de4112decd9c189da2a9d6ea5028e2c
--- /dev/null
+++ b/optis/backend/agent.py
@@ -0,0 +1,72 @@
+import gymnasium as gym
+import numpy as np
+import environment 
+from gymnasium.wrappers import FlattenObservation
+
+"""
+RL agent
+"""
+
+def q_learning(space, activities):
+    # Define the business process environment
+    env = environment.BusinessProcessEnv(space, activities)
+
+    # Define the Q-table
+    num_states = pow(2,14)
+    
+    num_actions = env.action_space.n
+
+    Q = np.zeros((num_states, num_actions), dtype = int)
+
+    # Set the hyperparameters
+    alpha = 0.1   # learning rate
+    gamma = 0.1  # discount factor
+    epsilon = 0.1 # exploration rate
+
+    mean_time = 0
+    mean_reward = 0
+
+    # Train the agent using Q-learning
+    num_episodes = 1000
+    for episode in range(num_episodes):
+        state, _ = env.reset()
+        state = env.flatten_observation_to_int(state)
+        done = False
+        start = env.process.env.now
+        while not done:
+            # Choose an action based on the epsilon-greedy policy
+            if np.random.uniform(0, 1) < epsilon:
+                action = env.action_space.sample()
+            else:
+                action = np.argmax(Q[state])
+            
+            
+            # Execute the action and observe the next state and reward
+            next_state, reward, done, _ = env.step(action)
+
+            # Update the Q-value for the current state-action pair
+            Q[state][action] = (1-alpha)*Q[state][action] + alpha * (reward + gamma * np.max(Q[next_state]) - Q[state][action])
+            #Q[state][action] = (1-alpha)*Q[state][action] + alpha*reward 
+            
+            # Transition to the next state
+            state = next_state
+
+        time = env.process.env.now - start 
+        mean_time += time
+        mean_reward += reward
+
+
+        if (episode % 20 == 19):
+            mean_reward /= 20
+            mean_time /= 20 
+            print(f"Episode {episode-19} to episode {episode}: mean time = {mean_time}, mean reward: {mean_reward}")
+        
+        if episode == 19:
+            start_reward = mean_reward
+        
+        if episode == 999:
+            end_reward = mean_reward
+            improvement = end_reward - start_reward
+            print(f"Reward improved by {improvement}")
+
+    return Q
diff --git a/optis/backend/environment.py b/optis/backend/environment.py
new file mode 100644
index 0000000000000000000000000000000000000000..9a7506b41753960fabec0f980a29ffc278c5fb98
--- /dev/null
+++ b/optis/backend/environment.py
@@ -0,0 +1,152 @@
+import gymnasium as gym
+import numpy as np
+import simpy
+import simplesimmodel as model
+
+"""
+Environment for the RL agent
+"""
+
+class BusinessProcessEnv(gym.Env):
+
+    def __init__(self, space, activities):
+        self.ressources = space[0]
+        self.case = space[1]
+        self.activities = activities
+        
+        self.observation_space = gym.spaces.Dict(
+            {
+                'process': gym.spaces.MultiDiscrete(self.ressources),
+                'case': gym.spaces.MultiDiscrete(self.case),
+                'event': gym.spaces.Discrete(self.activities)
+            }
+        )
+
+        self.action_space = gym.spaces.Discrete(self.activities)
+
+        self.current_state = {
+            'process': np.array(self.ressources),
+            'case': np.zeros(len(self.case), dtype=int),
+            'event': 0
+        }
+
+        self.model_env = simpy.Environment()
+        self.process = model.BusinessProcess(self.model_env, self.ressources)
+        self.model_env.process(model.run_process(self.model_env, self.process))
+
+        self.reward = 0
+
+
+    def get_current_state(self, caseid):
+        process, case, event = model.get_current_state(self.process, caseid)
+        state = {
+            'process': process,
+            'case': case,
+            'event': event
+        }
+        return state
+
+    def step(self, action):
+        
+        self.process.next = action
+
+        if self.process.case_id in self.process.done_cases:
+            self.process.case_id = np.random.choice(self.process.active_cases)
+        
+        case_obj = self.process.case_objects[self.process.case_id]
+        self.current_state = self.get_current_state(case_obj)
+
+        # print(f"{self.current_state['event']}")
+
+        start = self.process.env.now
+
+        self.process.flag = True
+
+        if self.process.is_valid(self.current_state['event'], action, case_obj):
+            
+            while(self.process.flag):
+                self.model_env.step()
+
+            stop = self.process.env.now
+
+            case_obj = self.process.case_objects[self.process.case_id]
+
+            # print(f"Agent did case {self.process.case_id} activity {action}.")
+
+            next_state = self.get_current_state(case_obj)
+            self.current_state = next_state
+            next_state = self.flatten_observation_to_int(next_state)
+
+            time = stop - start
+            reward = 10000 - time
+            self.reward += reward
+            done = True if (len(self.process.done_cases) == 5 or len(self.process.active_cases) == 0) else False
+            return next_state, self.reward, done, None
+        
+        else: 
+            self.reward += 0
+            next_state = self.flatten_observation_to_int(self.current_state)
+            done = False
+            return next_state, self.reward, done, None
+    
+         
+    def reset(self, seed=None, options=None):
+        # Reset the environment to the initial state
+        # Implement a function which extracts the current state from an event log / simulation model
+        super().reset(seed=seed)
+
+        self.current_state = {
+            'process': np.array(self.ressources),
+            'case': np.zeros(len(self.case), dtype=int),
+            'event': 0
+        }
+        self.current_step = 0
+
+        self.model_env = simpy.Environment()
+        self.process = model.BusinessProcess(self.model_env, self.ressources)
+        self.model_env.process(model.run_process(self.model_env, self.process))
+        self.process.done_cases = set([])
+
+        self.reward = 0
+
+        return self.current_state, None
+        
+
+    def render(self, mode='human'):
+        # Render the current state of the environment
+        pass
+
+    
+    def flatten_observation(self, observation):
+        flattened = []
+        for i in observation['process']: flattened.append(i)
+        for j in observation['case']: flattened.append(j)
+        flattened.append(observation['event'])
+
+        return flattened
+
+    def flatten_observation_to_int(self, observation):
+        state = 0
+        state += observation['event']*pow(2,10)
+        state += observation['case'][1]*pow(2,2)
+        state += observation['case'][2]*pow(2,2)
+        event = observation['event']
+        if event == 0:
+            state += observation['process'][0]*pow(2,6)
+        elif event == 1:
+            state += observation['process'][1]*pow(2,6)
+        elif 1 < event <=3:
+            state += observation['process'][2]*pow(2,6)+observation['process'][3]*pow(2,7)+observation['process'][4]*pow(2,8)
+        elif 3 < event <=6:
+            state += observation['process'][5]*pow(2,6)+observation['process'][6]*pow(2,7)
+        elif 6 < event <= 8:
+            state += observation['process'][7]*pow(2,6)+observation['process'][8]*pow(2,7)+observation['process'][9]*pow(2,8)
+        elif 8 < event <= 11:
+            state += observation['process'][10]*pow(2,6)+observation['process'][11]*pow(2,7)+observation['process'][12]*pow(2,8)
+        elif 11 < event <= 14:
+            state += observation['process'][0]*pow(2,6)
+        else:
+            pass
+        
+        return state
+
diff --git a/optis/backend/eventlog.py b/optis/backend/eventlog.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a3625ef7aca88f1debea601923e4acfb86ad3a5
--- /dev/null
+++ b/optis/backend/eventlog.py
@@ -0,0 +1,172 @@
+import pandas as pd
+import simplesimmodel as model
+import numpy as np
+
+"""
+Event log generator for our simulation model:
+- generate an event log
+- update an event log (adding new events)
+- export event log
+- get current state of an event log
+"""
+
+def add_start_event(process, event_id, case_id, activity, start_timestamp):
+    process.event_log.append(event_id)
+    process.event_log[event_id] = {
+        'CaseID': case_id,
+        'Activity': activity,
+        'StartTimestamp': float(start_timestamp),
+        'EndTimestamp': None
+    }
+    process.event_counter += 1
+
+def add_end_event(process, event_id, end_timestamp):
+    event = process.event_log[event_id]
+    event['EndTimestamp'] = end_timestamp
+
+
+def export_to_csv(process, file_path):
+    event_log_df = pd.DataFrame.from_dict(process.event_log)
+    event_log_df.to_csv(file_path, index=False)
+
+def export_to_xes(process, file_path):
+    # Use appropriate code to export to XES format
+    pass
+
+def get_active_cases():
+    event_log = pd.read_csv(r'D:\test\optis.csv')
+    active_cases = event_log.groupby('CaseID').filter(lambda x: 'order completed' not in x['Activity'].values)['CaseID'].unique().tolist()
+    return active_cases
+
+
+def get_state(case_id):
+
+    process = [] 
+    num_s = 1
+    process.append(num_s)
+    num_ot = 5
+    process.append(num_ot)
+    num_sh_a = 3
+    process.append(num_sh_a)
+    num_sh_b = 3
+    process.append(num_sh_b)
+    num_sh_c = 3
+    process.append(num_sh_c)
+    num_m_a = 3
+    process.append(num_m_a)
+    num_m_b = 2
+    process.append(num_m_b)
+    num_p_a = 4
+    process.append(num_p_a)
+    num_p_b = 5
+    process.append(num_p_b)
+    num_p_c = 4
+    process.append(num_p_c)
+    num_ds_a = 8
+    process.append(num_ds_a)
+    num_ds_b = 8
+    process.append(num_ds_b)
+    num_ds_c = 8
+    process.append(num_ds_c)
+
+    case = []
+    for i in range(15):
+        case.append(0)
+
+    activity_mapping = {
+        'place order': 1,
+        'arrange standard order': 2,
+        'arrange custom order': 3,
+        'pick from stock A': 4, 
+        'pick from stock B': 5, 
+        'pick from stock C': 6, 
+        'manufacture A': 7, 
+        'manufacture B': 8, 
+        'pack A': 9,
+        'pack B': 10,
+        'pack C': 11,
+        'attempt delivery A': 12,
+        'attempt delivery B': 13,
+        'attempt delivery C': 14,
+        'order completed': 15,
+    }
+
+    event_log = pd.read_csv(r'D:\test\optis.csv')
+    # Sort the event log by case ID and start timestamp
+    event_log.sort_values(by=['CaseID', 'StartTimestamp'], inplace=True)
+
+    # Group the event log by case ID and get the last activity for each case
+    last_activities = event_log.groupby('CaseID').tail(1).reset_index()
+   
+    # Remap the activity names to numbers using the mapping dictionary
+    last_activities['Activity'] = last_activities['Activity'].map(activity_mapping)
+
+    # Filter the cases where the end timestamp of the last activity is None or empty
+    unfinished_cases = last_activities[last_activities['EndTimestamp'].isnull()]['CaseID'].tolist()
+
+    # Update the state of the ressources given all unfinished cases
+    for i in unfinished_cases:
+        activity = last_activities[last_activities['CaseID'] == i]['Activity'].values[0]
+        if activity == 1 or activity == 15:
+            process[0] -= 1
+        elif activity == 2 or activity == 3:
+            process[1] -= 1
+        else:
+            process[activity-2] -= 1
+
+    # Get the state of the case for the given Case ID
+    filtered_log = event_log[event_log['CaseID'] == case_id]
+    activities = filtered_log['Activity'].map(activity_mapping).tolist()
+    for i in activities:
+        case[i-1] += 1
+
+    # Get the last event for the given Case ID
+    event = last_activities[last_activities['CaseID'] == case_id]['Activity'].values[0]
+
+    state = {
+        'process': process,
+        'case': case,
+        'event': event
+    }
+
+    print(state)
+
+    """
+    flattened = []
+    for i in state['process']: flattened.append(i)
+    for j in state['case']: flattened.append(j)
+    flattened.append(state['event'])
+    
+
+    flattened = 0
+    flattened += state['event']
+    for i in state['case']: flattened += i
+    for j in state['process']: flattened += j*process[j]
+
+    print(flattened)
+    """
+    flat_state = 0
+    flat_state += state['event']*pow(2,10)
+    print(flat_state)
+    flat_state += state['case'][1]*pow(2,1)
+    flat_state += state['case'][2]*pow(2,2)
+    event = state['event']
+    if event == 0:
+        flat_state += state['process'][0]*pow(2,6)
+    elif event == 1:
+        flat_state += state['process'][1]*pow(2,6)
+    elif 1 < event <=3:
+        flat_state += state['process'][2]*pow(2,6)+state['process'][3]*pow(2,7)+state['process'][4]*pow(2,8)
+    elif 3 < event <=6:
+        flat_state += state['process'][5]*pow(2,6)+state['process'][6]*pow(2,7)
+    elif 6 < event <= 8:
+        flat_state += state['process'][7]*pow(2,6)+state['process'][8]*pow(2,7)+state['process'][9]*pow(2,8)
+    elif 8 < event <= 11:
+        flat_state += state['process'][10]*pow(2,6)+state['process'][11]*pow(2,7)+state['process'][12]*pow(2,8)
+    elif 11 < event <= 14:
+        flat_state += state['process'][0]*pow(2,6)
+    else:
+        pass
+    
+    print(flat_state)
+    return flat_state
diff --git a/optis/backend/main.py b/optis/backend/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..fdee31d6eb3e8d2bf65037450ec61528c9b21529
--- /dev/null
+++ b/optis/backend/main.py
@@ -0,0 +1,91 @@
+import simpy
+import random
+import numpy as np
+import simplesimmodel as model
+import environment 
+import agent
+import eventlog as log
+import pandas as pd
+
+def main():
+    # Setup
+    # we can use a random seed if we want to generate the same results every time (maybe useful later for the training)
+    # random.seed(42)
+    # initialize the number of resources
+
+    process = [] 
+    num_s = 1
+    process.append(num_s)
+    num_ot = 5
+    process.append(num_ot)
+    num_sh_a = 3
+    process.append(num_sh_a)
+    num_sh_b = 3
+    process.append(num_sh_b)
+    num_sh_c = 3
+    process.append(num_sh_c)
+    num_m_a = 3
+    process.append(num_m_a)
+    num_m_b = 2
+    process.append(num_m_b)
+    num_p_a = 4
+    process.append(num_p_a)
+    num_p_b = 5
+    process.append(num_p_b)
+    num_p_c = 4
+    process.append(num_p_c)
+    num_ds_a = 7
+    process.append(num_ds_a)
+    num_ds_b = 7
+    process.append(num_ds_b)
+    num_ds_c = 7
+    process.append(num_ds_c)
+
+    case = []
+    for i in range(15):
+        case.append(1)
+        
+    space = [process, case]
+    activities = 16
+
+    # q learning
+    Q = agent.q_learning(space, activities)
+    # print(Q)
+
+    # generate event log
+    env = simpy.Environment()
+    business_process = model.BusinessProcess(env, process)
+    business_process.event_log_flag = True
+    env.process(model.run_process(env, business_process))
+    env.run(until = 10000)
+    log.export_to_csv(business_process, r'D:\test\optis.csv')
+
+    # extract active cases from event log
+    active_cases = log.get_active_cases()
+    print(active_cases)
+
+    # test agent 
+    for i in range(20):
+        caseid = random.choice(active_cases)
+        state = log.get_state(caseid)
+
+        action = np.argmax(Q[state])
+        print(action)
+        #print(Q)
+        print(Q[state])
+
+    state = Q[0]
+    action = np.argmax(state)
+    print(action)
+    print(state)
+
+    state = Q[64]
+    action = np.argmax(state)
+    print(action)
+    print(state)
+    
+
+
+
+if __name__ == "__main__":
+    main()
\ No newline at end of file
diff --git a/optis/backend/simplesimmodel.py b/optis/backend/simplesimmodel.py
new file mode 100644
index 0000000000000000000000000000000000000000..18cb95273cd687ac419a8d7c8cf3fce44011e738
--- /dev/null
+++ b/optis/backend/simplesimmodel.py
@@ -0,0 +1,553 @@
+import simpy
+import random
+import numpy as np
+import environment 
+import agent
+import eventlog as log
+
+"""
+Simulation model for a simpler business process, including:
+- a class representing the process
+- a function representing the process flow
+- a function to run the process
+"""
+
+
+""" 
+This class represents the process with its:
+- ressources: system, managers, stock handlers (a, b and c), manufacturers (a and b), delivery services (a, b and c)
+- activities
+"""
+class BusinessProcess(object):
+    """ Initialize the process with the number of the different types of ressources """ 
+    def __init__(self, env, ressources):
+        
+        # initialize simulation environment
+        self.env = env
+
+        # initialize ressources
+        self.system = simpy.Resource(env, ressources[0])
+        self.order_taker = simpy.Resource(env, ressources[1])
+        self.stock_handler_a = simpy.Resource(env, ressources[2])
+        self.stock_handler_b = simpy.Resource(env, ressources[3])
+        self.stock_handler_c = simpy.Resource(env, ressources[4])
+        self.manufacturer_a = simpy.Resource(env, ressources[5])
+        self.manufacturer_b = simpy.Resource(env, ressources[6])
+        self.packer_a = simpy.Resource(env, ressources[7])
+        self.packer_b = simpy.Resource(env, ressources[8])
+        self.packer_c = simpy.Resource(env, ressources[9])
+        # capacity of each delivery service instead of numbers of workers
+        self.delivery_service_a = simpy.Resource(env, ressources[10])
+        self.delivery_service_b = simpy.Resource(env, ressources[11])
+        self.delivery_service_c = simpy.Resource(env, ressources[12])
+    
+        # initialize lists with active cases and all case objects
+        self.active_cases = [0, 1, 2]
+        case_0 = Case(0)
+        case_1 = Case(1)
+        case_2 = Case(2)
+        self.case_objects = [case_0, case_1, case_2]
+        
+        # flag indicates whether the process is currently controlled by the agent and we set it on false every time the agent does an activity
+        self.flag = True
+
+        self.next = 0
+        
+        self.case_id = 0
+
+        self.done_cases = set([])
+
+        self.event_log_flag = False
+        self.event_log = []
+        self.event_counter = 0
+
+        # random.seed(1)
+
+    def place_order(self, case):
+        yield self.env.timeout(0)
+
+    def arrange_standard_order(self, case):
+        yield self.env.timeout(random.randint(10, 15))
+    
+    def arrange_custom_order(self, case):
+        yield self.env.timeout(random.randint(20, 30))
+    
+    def pick_from_stock_a(self, case):
+        yield self.env.timeout(random.randint(10, 40))
+    
+    def pick_from_stock_b(self, case):
+        yield self.env.timeout(random.randint(20, 60))
+
+    def pick_from_stock_c(self, case):
+        yield self.env.timeout(random.randint(30, 80))
+    
+    def manufacture_a(self, case):
+        yield self.env.timeout(random.randint(240, 360))
+    
+    def manufacture_b(self, case):
+        yield self.env.timeout(random.randint(360, 480))
+    
+    def pack_a(self, case):
+        yield self.env.timeout(random.randint(10, 20))
+    
+    def pack_b(self, case):
+        yield self.env.timeout(random.randint(15, 30))
+    
+    def pack_c(self, case):
+        yield self.env.timeout(random.randint(25, 50))
+    
+    def attempt_delivery_a(self, case):
+        yield self.env.timeout(random.randint(720, 1440))
+    
+    def attempt_delivery_b(self, case):
+        yield self.env.timeout(random.randint(1440, 2160))
+
+    def attempt_delivery_c(self, case):
+        yield self.env.timeout(random.randint(1440, 2880))
+    
+    def order_completed(self, case):
+        yield self.env.timeout(1)
+    
+    def is_valid(self, event, action, case_obj):
+        if event == 0 and action == 1:
+            return True
+        elif event == 1 and (action == 2 or action == 3):
+            return True
+        elif event == 2 and case_obj.standard_order and (action == 4 or action == 5 or action == 6):
+            return True
+        elif event == 3 and (not case_obj.standard_order) and (action == 7 or action == 8):
+            return True
+        elif (4 <= event <= 8) and (action == 9 or action == 10 or action == 11):
+            return True
+        elif (9 <= event <= 11) and (12 <= action <=14):
+            return True
+        elif (12 <= event <=14) and action == 15:
+            return True
+        else:
+            return False
+
+"""
+A class representing a single case from the process. Every case has an (unique) case id, 
+state showing which activities have been already done, current activity being executed,
+agent flag showing if the agent is currently controlling the case and 
+a flag whether the order is standard
+"""
+class Case(object):
+    def __init__(self, case):
+        self.case_id = case
+        self.state = np.zeros(15, dtype = int)
+        self.current = 0
+        self.agent = False 
+        self.standard_order = True
+
+"""
+A function, which represents the process model with it's control flow and ressource allocation.
+A customer places an order at the system and then is redirected to the order takers, where it's arranged 
+what type of order the customer wants - standard or custom. After that depending on whether the order 
+is standard or custom, respectively a stock or manufacturer is chosen. 
+Afterwards one of three packing options is chosen and executed. 
+Following the packing one of the three delivery services deliver the order. 
+With that the order is completed.
+"""
+def execute_case(env, case, process):
+    # create a case object to keep track of case attributes if not already existing
+    if len(process.case_objects) <= case:
+        case_obj = Case(case)
+
+        # add the case to the process's active cases list
+        process.case_objects.append(case_obj) 
+        process.active_cases.append(case) 
+
+    # if the case is one of the first three choose it from the list
+    if len(process.case_objects) <= 3:
+        case_obj = process.case_objects[case]
+
+    # if case chosen by agent set agent flag to true
+    if process.case_id == case: 
+        case_obj.agent = True
+
+    # place order
+    case_obj.state[0] += 1
+    case_obj.current = 1
+    with process.system.request() as request:
+        yield request
+        if process.event_log_flag:
+            event_counter = process.event_counter
+            log.add_start_event(process, event_counter, case, "place order", env.now)
+        yield env.process(process.place_order(case))
+        if process.event_log_flag: 
+            log.add_end_event(process, event_counter, env.now)
+        # if case_obj.agent: print(f"Case {case}: 'placed order' at {env.now:.2f}")
+
+
+    # if the last action was made from the agent set the process flag to be able to return to the environment's step function
+    if case_obj.agent: 
+        process.flag = False
+
+    with process.system.request() as request:
+        yield request
+
+    # before a new action is executed check if the agent is controlling the case and set the flag to true if yes
+    if process.case_id == case: case_obj.agent = True 
+
+    # standard order XOR custom order
+    choice = random.randint(2,3) if not case_obj.agent else process.next 
+    if choice == 2:
+        case_obj.standard_order = True
+        case_obj.state[1] += 1
+        case_obj.current = 2
+        with process.order_taker.request() as request:
+            yield request
+            # if case_obj.agent: print(f"Case {case}: started 'arrange standard order' at {env.now:.2f}")
+            if process.event_log_flag:
+                event_counter = process.event_counter
+                log.add_start_event(process, event_counter, case, "arrange standard order", env.now)
+            yield env.process(process.arrange_standard_order(case))
+            if process.event_log_flag: 
+                log.add_end_event(process, event_counter, env.now)
+            # if case_obj.agent: print(f"Case {case}: finished 'arrange standard order' at {env.now:.2f}")
+            if case_obj.agent: 
+                process.flag = False
+            with process.system.request() as request:
+                    yield request
+    else:
+        case_obj.state[2] += 1
+        case_obj.current = 3
+        case_obj.standard_order = False
+        with process.order_taker.request() as request:
+            yield request
+            # if case_obj.agent: print(f"Case {case}: started 'arrange custom order' at {env.now:.2f}")
+            if process.event_log_flag:
+                event_counter = process.event_counter
+                log.add_start_event(process, event_counter, case, "arrange custom order", env.now)
+            yield env.process(process.arrange_custom_order(case))
+            if process.event_log_flag: 
+                log.add_end_event(process, event_counter, env.now)
+            # if case_obj.agent: print(f"Case {case}: finished 'arrange custom order' at {env.now:.2f}")
+            if case_obj.agent: 
+                process.flag = False
+            with process.system.request() as request:
+                    yield request
+
+
+    if process.case_id == case: case_obj.agent = True 
+
+    if case_obj.standard_order and (not case_obj.agent):
+        choice = random.randint(4,6)
+    elif (not case_obj.standard_order) and (not case_obj.agent):
+        choice = random.randint(7,8)
+    else:
+        choice = process.next
+     
+    # choose stock or manufacturer
+    if choice == 4:
+        case_obj.state[3] += 1
+        case_obj.current = 4
+        with process.stock_handler_a.request() as request:
+            yield request
+            # if case_obj.agent: print(f"Case {case}: started 'pick from stock A' at {env.now:.2f}")
+            if process.event_log_flag:
+                event_counter = process.event_counter
+                log.add_start_event(process, event_counter, case, "pick from stock A", env.now)
+            yield env.process(process.pick_from_stock_a(case))
+            if process.event_log_flag: 
+                log.add_end_event(process, event_counter, env.now)
+            # if case_obj.agent: print(f"Case {case}: finished 'pick from stock A' at {env.now:.2f}")
+            if case_obj.agent: 
+                process.flag = False
+            with process.system.request() as request:
+                yield request
+    elif choice == 5:
+        case_obj.state[4] += 1
+        case_obj.current = 5
+        with process.stock_handler_b.request() as request:
+            yield request
+            # if case_obj.agent: print(f"Case {case}: started 'pick from stock B' at {env.now:.2f}")
+            if process.event_log_flag:
+                event_counter = process.event_counter
+                log.add_start_event(process, event_counter, case, "pick from stock B", env.now)
+            yield env.process(process.pick_from_stock_b(case))
+            if process.event_log_flag: 
+                log.add_end_event(process, event_counter, env.now)
+            
+            # if case_obj.agent: print(f"Case {case}: finished 'pick from stock B' at {env.now:.2f}")
+            if case_obj.agent: 
+                process.flag = False
+            with process.system.request() as request:
+                yield request
+    elif choice == 6:
+        case_obj.state[5] += 1
+        case_obj.current = 6
+        with process.stock_handler_c.request() as request:
+            yield request
+            # if case_obj.agent: print(f"Case {case}: started 'pick from stock C' at {env.now:.2f}")
+            if process.event_log_flag:
+                event_counter = process.event_counter
+                log.add_start_event(process, event_counter, case, "pick from stock C", env.now)
+            yield env.process(process.pick_from_stock_c(case))
+            if process.event_log_flag: 
+                log.add_end_event(process, event_counter, env.now)
+            # if case_obj.agent: print(f"Case {case}: finished 'pick from stock C' at {env.now:.2f}")
+            if case_obj.agent: 
+                process.flag = False
+            with process.system.request() as request:
+                yield request
+    elif choice == 7:
+        case_obj.state[6] += 1
+        case_obj.current = 7
+        with process.manufacturer_a.request() as request:
+            yield request
+            # if case_obj.agent: print(f"Case {case}: started 'manufacture A' at {env.now:.2f}")
+            if process.event_log_flag:
+                event_counter = process.event_counter
+                log.add_start_event(process, event_counter, case, "manufacture A", env.now)
+            yield env.process(process.manufacture_a(case))
+            if process.event_log_flag: 
+                log.add_end_event(process, event_counter, env.now)
+            
+            # if case_obj.agent: print(f"Case {case}: finished 'manufacture A' at {env.now:.2f}")
+            if case_obj.agent: 
+                process.flag = False
+            with process.system.request() as request:
+                yield request
+    else:
+        case_obj.state[7] += 1
+        case_obj.current = 8
+        with process.manufacturer_b.request() as request:
+            yield request
+            # if case_obj.agent: print(f"Case {case}: started 'manufacture B' at {env.now:.2f}")
+            if process.event_log_flag:
+                event_counter = process.event_counter
+                log.add_start_event(process, event_counter, case, "manufacture B", env.now)
+            yield env.process(process.manufacture_b(case))
+            if process.event_log_flag: 
+                log.add_end_event(process, event_counter, env.now)
+            # if case_obj.agent: print(f"Case {case}: finished 'manufacture B' at {env.now:.2f}")
+            if case_obj.agent: 
+                process.flag = False
+            with process.system.request() as request:
+                yield request
+
+
+    if process.case_id == case: case_obj.agent = True 
+
+    choice = random.randint(9,11) if not case_obj.agent else process.next
+
+    if choice == 9:
+        case_obj.state[8] += 1
+        case_obj.current = 9
+        with process.packer_a.request() as request:
+            yield request
+            # if case_obj.agent: print(f"Case {case}: started 'pack A' at {env.now:.2f}")
+            if process.event_log_flag:
+                event_counter = process.event_counter
+                log.add_start_event(process, event_counter, case, "pack A", env.now)
+            yield env.process(process.pack_a(case))
+            if process.event_log_flag: 
+                log.add_end_event(process, event_counter, env.now)
+            
+            # if case_obj.agent: print(f"Case {case}: finished 'pack A' at {env.now:.2f}")
+            if case_obj.agent: 
+                process.flag = False
+            with process.system.request() as request:
+                yield request
+    elif choice == 10:
+        case_obj.state[9] += 1
+        case_obj.current = 10
+        with process.packer_b.request() as request:
+            yield request
+            # if case_obj.agent: print(f"Case {case}: started 'pack B' at {env.now:.2f}")
+            if process.event_log_flag:
+                event_counter = process.event_counter
+                log.add_start_event(process, event_counter, case, "pack B", env.now)
+            yield env.process(process.pack_b(case))
+            if process.event_log_flag: 
+                log.add_end_event(process, event_counter, env.now)
+            
+            # if case_obj.agent: print(f"Case {case}: finished 'pack B' at {env.now:.2f}")
+            if case_obj.agent: 
+                process.flag = False
+
+            with process.system.request() as request:
+                yield request
+    else:
+        case_obj.state[10] += 1
+        case_obj.current = 11
+        with process.packer_c.request() as request:
+            yield request
+            # if case_obj.agent: print(f"Case {case}: started 'pack C' at {env.now:.2f}")
+            if process.event_log_flag:
+                event_counter = process.event_counter
+                log.add_start_event(process, event_counter, case, "pack C", env.now)
+            yield env.process(process.pack_c(case))
+            if process.event_log_flag: 
+                log.add_end_event(process, event_counter, env.now)
+            
+            # if case_obj.agent: print(f"Case {case}: finished 'pack C' at {env.now:.2f}")
+            if case_obj.agent: 
+                process.flag = False
+            with process.system.request() as request:
+                yield request
+
+    if process.case_id == case: case_obj.agent = True 
+
+    # choose delivery
+    choice = random.randint(12,14) if not case_obj.agent else process.next
+    if choice == 12:
+        case_obj.state[11] += 1
+        case_obj.current = 12
+        with process.delivery_service_a.request() as request:
+            yield request
+            # if case_obj.agent: print(f"Case {case}: started 'attempt delivery A' at {env.now:.2f}")
+            if process.event_log_flag:
+                event_counter = process.event_counter
+                log.add_start_event(process, event_counter, case, "attempt delivery A", env.now)
+            yield env.process(process.attempt_delivery_a(case))
+            if process.event_log_flag: 
+                log.add_end_event(process, event_counter, env.now)
+            
+            # if case_obj.agent: print(f"Case {case}: finished 'attempt delivery A' at {env.now:.2f}")
+            if case_obj.agent: 
+                process.flag = False
+            with process.system.request() as request:
+                yield request
+    elif choice == 13:
+        case_obj.state[12] += 1
+        case_obj.current = 13
+        with process.delivery_service_b.request() as request:
+            yield request
+            # if case_obj.agent: print(f"Case {case}: started 'attempt delivery B' at {env.now:.2f}")
+            if process.event_log_flag:
+                event_counter = process.event_counter
+                log.add_start_event(process, event_counter, case, "attempt delivery B", env.now)
+            yield env.process(process.attempt_delivery_b(case))
+            if process.event_log_flag: 
+                log.add_end_event(process, event_counter, env.now)
+            
+            # if case_obj.agent: print(f"Case {case}: finished 'attempt delivery B' at {env.now:.2f}")
+            if case_obj.agent: 
+                process.flag = False
+            with process.system.request() as request:
+                yield request
+    else: 
+        case_obj.state[13] += 1
+        case_obj.current = 14
+        with process.delivery_service_c.request() as request:
+            yield request
+            # if case_obj.agent: print(f"Case {case}: started 'attempt delivery C' at {env.now:.2f}")
+            if process.event_log_flag:
+                event_counter = process.event_counter
+                log.add_start_event(process, event_counter, case, "attempt delivery C", env.now)
+            yield env.process(process.attempt_delivery_c(case))
+            if process.event_log_flag: 
+                log.add_end_event(process, event_counter, env.now)
+            
+            # if case_obj.agent: print(f"Case {case}: finished 'attempt delivery C' at {env.now:.2f}")
+            if case_obj.agent: 
+                process.flag = False
+            with process.system.request() as request:
+                yield request
+    
+
+    if process.case_id == case: case_obj.agent = True 
+
+    # case completed
+    case_obj.state[14] += 1
+    case_obj.current = 15
+    with process.system.request() as request:
+        yield request
+        if process.event_log_flag:
+            event_counter = process.event_counter
+            log.add_start_event(process, event_counter, case, "order completed", env.now)
+        yield env.process(process.order_completed(case))
+        if process.event_log_flag: 
+            log.add_end_event(process, event_counter, env.now)
+        # if case_obj.agent: print(f"Case {case}: 'completed' at {env.now:.2f}")
+
+
+    if case in process.active_cases:
+        process.active_cases.remove(case)
+    
+    if case_obj.agent: 
+        for i in process.case_objects:
+            if (i.current == 15) and (i.case_id in process.active_cases) and (i.case_id != case):
+                process.active_cases.remove(i.case_id)
+        process.done_cases.add(process.case_id)
+        process.flag = False 
+    
+
+"""
+Get the cureent state of the process and a specific case
+- available ressouces
+- events which already happened in the case
+- current event of the case
+"""
+def get_current_state(process, case):
+    process_state = []
+
+    num_system = process.system.capacity - process.system.count
+    process_state.append(num_system)
+
+    num_order_taker = process.order_taker.capacity - process.order_taker.count 
+    process_state.append(num_order_taker)
+
+    num_stock_handler_a = process.stock_handler_a.capacity - process.stock_handler_a.count 
+    process_state.append(num_stock_handler_a)
+
+    num_stock_handler_b = process.stock_handler_b.capacity - process.stock_handler_b.count 
+    process_state.append(num_stock_handler_b)
+
+    num_stock_handler_c = process.stock_handler_c.capacity - process.stock_handler_c.count 
+    process_state.append(num_stock_handler_c)
+
+    num_manufacturer_a = process.manufacturer_a.capacity - process.manufacturer_a.count 
+    process_state.append(num_manufacturer_a)
+
+    num_manufacturer_b = process.manufacturer_b.capacity - process.manufacturer_b.count 
+    process_state.append(num_manufacturer_b)
+
+    num_packer_a = process.packer_a.capacity - process.packer_a.count 
+    process_state.append(num_packer_a)
+
+    num_packer_b = process.packer_b.capacity - process.packer_b.count 
+    process_state.append(num_packer_b)
+
+    num_packer_c = process.packer_c.capacity - process.packer_c.count 
+    process_state.append(num_packer_c)
+
+    num_delivery_service_a = process.delivery_service_a.capacity - process.delivery_service_a.count 
+    process_state.append(num_delivery_service_a)
+
+    num_delivery_service_b = process.delivery_service_b.capacity - process.delivery_service_b.count
+    process_state.append(num_delivery_service_b) 
+
+    num_delivery_service_c = process.delivery_service_c.capacity - process.delivery_service_c.count 
+    process_state.append(num_delivery_service_c)
+
+    cur_case = case.state
+
+    event  = case.current
+
+    return process_state, cur_case, event
+
+"""
+Defines how often new orders (cases) come in and starts executing them
+"""
+def run_process(env, process):
+    # process = Process(env, num_ot, num_m, num_sh_a, num_sh_b, num_sh_c, num_m_a, num_m_b, num_ds_a, num_ds_b, num_ds_c)
+    
+    # the waiting orders
+    for case in range(3):
+        env.process(execute_case(env, case, process))
+
+    # the new incoming orders
+    while case < 1000:
+        waittime = random.randint(10,15)
+        if case % 20 == 0:
+            waittime = 100
+        yield env.timeout(waittime)  # Wait a bit before generating a new case
+
+        case += 1
+        # process.active_cases.append(case)
+        env.process(execute_case(env, case, process))
+