12 Commits

4 changed files with 87 additions and 48 deletions

View File

@@ -2,6 +2,7 @@
FROM python:3.11 FROM python:3.11
COPY src /app/src COPY src /app/src
COPY graphics /app/graphics
COPY requirements.txt /app/src COPY requirements.txt /app/src
# set the working directory in the container # set the working directory in the container
@@ -16,4 +17,4 @@ RUN rm requirements.txt
# command to run on container start # command to run on container start
ENTRYPOINT ["streamlit", "run"] ENTRYPOINT ["streamlit", "run"]
CMD ["start_page.py"] CMD ["start_app.py"]

BIN
graphics/kausaldiagramm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View File

@@ -55,11 +55,11 @@ class RequestLoadEstimator:
AVERAGE_PROBLEM_LEVEL_IN_PERCENT = 0.1 AVERAGE_PROBLEM_LEVEL_IN_PERCENT = 0.1
def __init__(self): def __init__(self):
self._weeks_cycle = 4 self._days_cycle = 4
self.service_desk_employees = 0 self.service_desk_employees = 0
self.documentation_level = 0 self.documentation_level = 0
self._service_desk_knowledge_level = 0 self.service_desk_knowledge_level = 0
self._systems_complexity = 0 self.systems_complexity = 0
self.nr_of_applications = 0 self.nr_of_applications = 0
self.nr_of_employees = 0 self.nr_of_employees = 0
self.new_application_problems = 0 self.new_application_problems = 0
@@ -70,10 +70,14 @@ class RequestLoadEstimator:
self.actual_week = 0 self.actual_week = 0
self.data_set = DataFrame() self.data_set = DataFrame()
self.initialize_data() self.initialize_data()
self.prev_dict = {"day": 0, "days_cycle": 0, "service_desk_employees": 0, "documentation_level": 0,
"service_desk_knowledge_level": 0, "systems_complexity": 0, "nr_of_applications": 0,
"new_application_problems": 0, "nr_of_open_service_requests": 0, "request_working_time": 0,
"personal_available_time": 0, "processed_service_requests": 0, "nr_of_employees": 0}
pass pass
def set_weeks_cycle(self, days: int): def set_weeks_cycle(self, days: int):
self._weeks_cycle = days self._days_cycle = days
def add_new_applications(self, nr: int): def add_new_applications(self, nr: int):
if nr > 0: if nr > 0:
@@ -83,29 +87,29 @@ class RequestLoadEstimator:
if self.documentation_level < DOCUMENTATION_LEVEL_MIN: if self.documentation_level < DOCUMENTATION_LEVEL_MIN:
self.documentation_level = DOCUMENTATION_LEVEL_MIN self.documentation_level = DOCUMENTATION_LEVEL_MIN
complexity_increase = (nr / self.nr_of_applications) * 0.2 complexity_increase = (nr / self.nr_of_applications) * 0.2
self._systems_complexity += complexity_increase self.systems_complexity += complexity_increase
self.nr_of_applications += nr self.nr_of_applications += nr
elif nr < 0: elif nr < 0:
logger.info(MODULE_LOGGER_HEAD + "Removing applications: " + str(nr)) logger.info(MODULE_LOGGER_HEAD + "Removing applications: " + str(nr))
complexity_reduction = (nr / self.nr_of_applications) * 0.2 complexity_reduction = (nr / self.nr_of_applications) * 0.2
self._systems_complexity -= complexity_reduction self.systems_complexity -= complexity_reduction
self.nr_of_applications += nr self.nr_of_applications += nr
def add_new_employees(self, nr: int): def add_new_employees(self, nr: int):
if nr > 0: if nr > 0:
logger.info(MODULE_LOGGER_HEAD + "Adding new employees: " + str(nr)) logger.info(MODULE_LOGGER_HEAD + "Adding new employees: " + str(nr))
self._systems_complexity += nr / self.nr_of_employees self.systems_complexity += nr / self.nr_of_employees
self.nr_of_employees += nr self.nr_of_employees += nr
elif nr < 0: elif nr < 0:
logger.info(MODULE_LOGGER_HEAD + "Removing employees: " + str(nr)) logger.info(MODULE_LOGGER_HEAD + "Removing employees: " + str(nr))
self._systems_complexity -= nr / self.nr_of_employees self.systems_complexity -= nr / self.nr_of_employees
self.nr_of_employees += nr self.nr_of_employees += nr
def add_service_desk_employees(self, nr: int): def add_service_desk_employees(self, nr: int):
if nr > 0: if nr > 0:
logger.info(MODULE_LOGGER_HEAD + "Adding new service desk employees: " + str(nr)) logger.info(MODULE_LOGGER_HEAD + "Adding new service desk employees: " + str(nr))
knowledge_level_reduction = (nr / self.service_desk_employees) * 0.2 knowledge_level_reduction = (nr / self.service_desk_employees) * 0.2
self._service_desk_knowledge_level = limit_value(self._service_desk_knowledge_level - knowledge_level_reduction, self.service_desk_knowledge_level = limit_value(self.service_desk_knowledge_level - knowledge_level_reduction,
KNOWLEDGE_LEVEL_MIN, KNOWLEDGE_LEVEL_MIN,
KNOWLEDGE_LEVEL_MAX) KNOWLEDGE_LEVEL_MAX)
self.service_desk_employees += nr self.service_desk_employees += nr
@@ -115,7 +119,7 @@ class RequestLoadEstimator:
def process_weeks(self): def process_weeks(self):
logger.info(MODULE_LOGGER_HEAD + "Processing weeks") logger.info(MODULE_LOGGER_HEAD + "Processing weeks")
for week in range(self._weeks_cycle): for day in range(self._days_cycle):
self._calc_nr_of_service_requests() self._calc_nr_of_service_requests()
self._calc_request_working_time() self._calc_request_working_time()
self._calc_personal_available_time() self._calc_personal_available_time()
@@ -127,11 +131,11 @@ class RequestLoadEstimator:
def initialize_data(self): def initialize_data(self):
logger.info(MODULE_LOGGER_HEAD + "Initializing data") logger.info(MODULE_LOGGER_HEAD + "Initializing data")
self._weeks_cycle = 7 self._days_cycle = 7
self.service_desk_employees = START_NR_OF_SERVICE_DESK_EMPLOYEES self.service_desk_employees = START_NR_OF_SERVICE_DESK_EMPLOYEES
self.documentation_level = START_DOCUMENTATION_LEVEL self.documentation_level = START_DOCUMENTATION_LEVEL
self._service_desk_knowledge_level = START_KNOWLEDGE_LEVEL self.service_desk_knowledge_level = START_KNOWLEDGE_LEVEL
self._systems_complexity = START_SYSTEMS_COMPLEXITY self.systems_complexity = START_SYSTEMS_COMPLEXITY
self.nr_of_applications = START_NR_OF_APPLICATIONS self.nr_of_applications = START_NR_OF_APPLICATIONS
self.nr_of_employees = START_NR_OF_EMPLOYEES self.nr_of_employees = START_NR_OF_EMPLOYEES
self.new_application_problems = 0 self.new_application_problems = 0
@@ -144,7 +148,7 @@ class RequestLoadEstimator:
def _calc_application_problems(self): def _calc_application_problems(self):
self.new_application_problems = ((self.nr_of_employees * self.nr_of_applications * self.new_application_problems = ((self.nr_of_employees * self.nr_of_applications *
self._systems_complexity / self.documentation_level) self.systems_complexity / self.documentation_level)
* self.AVERAGE_PROBLEM_LEVEL_IN_PERCENT) * self.AVERAGE_PROBLEM_LEVEL_IN_PERCENT)
if self.new_application_problems < 0: if self.new_application_problems < 0:
self.new_application_problems = 0 self.new_application_problems = 0
@@ -155,7 +159,7 @@ class RequestLoadEstimator:
def _calc_request_working_time(self): def _calc_request_working_time(self):
self._desired_request_working_time = (self.nr_of_open_service_requests * self._desired_request_working_time = (self.nr_of_open_service_requests *
(self.AVERAGE_REQUEST_WORKING_TIME / self._service_desk_knowledge_level)) (self.AVERAGE_REQUEST_WORKING_TIME / self.service_desk_knowledge_level))
if self._desired_request_working_time < 0: if self._desired_request_working_time < 0:
self._desired_request_working_time = 0 self._desired_request_working_time = 0
@@ -166,7 +170,7 @@ class RequestLoadEstimator:
else: else:
self.processed_service_requests = int((self.service_desk_employees * self.AVERAGE_DAY_WORKING_TIME) / self.processed_service_requests = int((self.service_desk_employees * self.AVERAGE_DAY_WORKING_TIME) /
( (
self.AVERAGE_REQUEST_WORKING_TIME / self._service_desk_knowledge_level)) self.AVERAGE_REQUEST_WORKING_TIME / self.service_desk_knowledge_level))
self.nr_of_open_service_requests -= self.processed_service_requests self.nr_of_open_service_requests -= self.processed_service_requests
@@ -180,27 +184,29 @@ class RequestLoadEstimator:
pass pass
def _calc_documentation_and_knowledge_level(self): def _calc_documentation_and_knowledge_level(self):
self.documentation_level += (self.personal_available_time * KNOWLEDGE_REDUCTION_FACTOR / documentation_level_reduction = (self.personal_available_time * KNOWLEDGE_REDUCTION_FACTOR /
(self.nr_of_employees * self.nr_of_applications)) (self.nr_of_employees * self.nr_of_applications))
self.documentation_level += documentation_level_reduction if documentation_level_reduction >= 0 else documentation_level_reduction / 10
self._service_desk_knowledge_level += self.personal_available_time * KNOWLEDGE_REDUCTION_FACTOR / ( know_how_reduction = self.personal_available_time * KNOWLEDGE_REDUCTION_FACTOR / (
self.nr_of_employees * self.nr_of_employees *
self.nr_of_applications *
self.service_desk_employees) self.service_desk_employees)
self.service_desk_knowledge_level += know_how_reduction if know_how_reduction >= 0 else know_how_reduction / 10
self.documentation_level = limit_value(self.documentation_level, DOCUMENTATION_LEVEL_MIN, self.documentation_level = limit_value(self.documentation_level, DOCUMENTATION_LEVEL_MIN,
DOCUMENTATION_LEVEL_MAX) DOCUMENTATION_LEVEL_MAX)
self._service_desk_knowledge_level = limit_value(self._service_desk_knowledge_level, KNOWLEDGE_LEVEL_MIN, self.service_desk_knowledge_level = limit_value(self.service_desk_knowledge_level, KNOWLEDGE_LEVEL_MIN,
KNOWLEDGE_LEVEL_MAX) KNOWLEDGE_LEVEL_MAX)
def _process_data_set(self): def _process_data_set(self):
row = { row = {
"week": self.actual_week, "day": self.actual_week,
"weeks_cycle": self._weeks_cycle, "days_cycle": self._days_cycle,
"service_desk_employees": self.service_desk_employees, "service_desk_employees": self.service_desk_employees,
"documentation_level": self.documentation_level, "documentation_level": self.documentation_level,
"service_desk_knowledge_level": self._service_desk_knowledge_level, "service_desk_knowledge_level": self.service_desk_knowledge_level,
"systems_complexity": self._systems_complexity, "systems_complexity": self.systems_complexity,
"nr_of_applications": self.nr_of_applications, "nr_of_applications": self.nr_of_applications,
"new_application_problems": self.new_application_problems, "new_application_problems": self.new_application_problems,
"nr_of_open_service_requests": self.nr_of_open_service_requests, "nr_of_open_service_requests": self.nr_of_open_service_requests,

View File

@@ -38,6 +38,17 @@ FOOTER = f"""
{APP_NAME} - v{APP_VERSION} - by <a href="https://www.bit-buddy.at" target="_blank">BitBuddySolutions</a> {APP_NAME} - v{APP_VERSION} - by <a href="https://www.bit-buddy.at" target="_blank">BitBuddySolutions</a>
</div> </div>
""" """
DESCRIPTION_MARKDOWN = """
This app is designed to help estimate the complexity of a service request system to avoid problems within your IT department.
The estimator is based on the following assumptions:
- If the number of servicedesk employess is not sufficient, the number of open service requests will increase.
- If new employees are added or new applications are introduced, the number of open service requests will increase.
- If the documentation level is low, the number of open service requests will increase.
- If the knowledge level of the service desk employees is low, the required time to process a service request will increase.
- If the service desk employees have available time, they will increase the documentation level.
"""
# --------------------------------------- # # --------------------------------------- #
# global vars # # global vars #
# --------------------------------------- # # --------------------------------------- #
@@ -58,6 +69,14 @@ def get_estimator():
return RequestLoadEstimator() return RequestLoadEstimator()
def write_metric(estimator_object: RequestLoadEstimator, st_object: st, title: str, value_name: str,
delta_color="normal"):
value = getattr(estimator_object, value_name)
delta = value - estimator_object.prev_dict[value_name]
estimator_object.prev_dict[value_name] = value
st_object.metric(title, f"{value:0.2f}", delta=f"{delta:0.2f}", delta_color=delta_color)
# --------------------------------------- # # --------------------------------------- #
# classes # # classes #
# --------------------------------------- # # --------------------------------------- #
@@ -70,15 +89,23 @@ if __name__ == "__main__":
setup_logging() setup_logging()
st.header("Service Request Complexity Estimator") st.header("Service Request Complexity Estimator")
st.write("This app is designed to help estimate the complexity of a service request system to avoid problems " base_col1, base_col2 = st.columns([1, 1])
"within your IT department.")
base_col1.markdown(DESCRIPTION_MARKDOWN)
base_col2.write("The estimator is based on the following diagram")
base_col2.image("../graphics/kausaldiagramm.png", caption="Causal Diagram")
st.divider()
st.subheader("Estimator")
form = st.form(key='complex_form') form = st.form(key='complex_form')
col1, col2, col3, col4 = form.columns(4) col1, col2, col3, col4 = form.columns(4)
form.divider() form.divider()
dis_col1, dis_col2, dis_col3 = form.columns([2, 2, 1]) dis_col1, dis_col2, dis_col3 = form.columns([2, 2, 1])
weeks_cycle = col1.number_input("Weeks cycle", min_value=1, max_value=20, value=3) days_cycle = col1.number_input("Days cycle", min_value=1, max_value=20, value=3)
add_new_employee = col2.number_input("Add new employees", min_value=0, max_value=50, value=0) add_new_employee = col2.number_input("Change number of employees", min_value=-50, max_value=50, value=0)
add_new_applications = col3.number_input("Change number of applications", min_value=-10, max_value=10, value=0) add_new_applications = col3.number_input("Change number of applications", min_value=-10, max_value=10, value=0)
add_new_service_desk_employees = col4.number_input("Change number of service desk employees", min_value=-10, add_new_service_desk_employees = col4.number_input("Change number of service desk employees", min_value=-10,
max_value=10, max_value=10,
@@ -89,46 +116,51 @@ if __name__ == "__main__":
request_estimator = get_estimator() request_estimator = get_estimator()
if submit_button: if submit_button:
request_estimator.set_weeks_cycle(weeks_cycle) request_estimator.set_weeks_cycle(days_cycle)
request_estimator.add_new_applications(add_new_applications) request_estimator.add_new_applications(add_new_applications)
request_estimator.add_service_desk_employees(add_new_service_desk_employees) request_estimator.add_service_desk_employees(add_new_service_desk_employees)
request_estimator.add_new_employees(add_new_employee) request_estimator.add_new_employees(add_new_employee)
request_estimator.process_weeks() request_estimator.process_weeks()
if not request_estimator.data_set.empty: if not request_estimator.data_set.empty:
fig_requests = px.area(request_estimator.data_set, x="week", y=["nr_of_processed_service_requests", fig_requests = px.area(request_estimator.data_set, x="day", y=["nr_of_processed_service_requests",
"nr_of_open_service_requests"], "nr_of_open_service_requests"],
line_shape="spline") line_shape="spline")
fig_personal = px.area(request_estimator.data_set, x="week", y=["personal_available_time", fig_personal = px.area(request_estimator.data_set, x="day", y=["personal_available_time",
"request_working_time", "request_working_time",
"service_desk_employees", "service_desk_employees",
"nr_of_employees"], "nr_of_employees"],
line_shape="spline") line_shape="spline")
fig_knowledge = px.area(request_estimator.data_set, x="week", y=["documentation_level", fig_knowledge = px.area(request_estimator.data_set, x="day", y=["documentation_level",
"service_desk_knowledge_level", "service_desk_knowledge_level",
"systems_complexity"], "systems_complexity"],
line_shape="spline") line_shape="spline")
dis_col1.subheader("Service Request Overview") dis_col1.subheader("Service Request Overview")
dis_col1.plotly_chart(fig_requests, use_container_width=True) dis_col1.plotly_chart(fig_requests, use_container_width=True)
dis_col2.subheader("Personal and Knowledge Overview") dis_col1.subheader("Monitoring Metrics")
sub_col1, sub_col2 = dis_col1.columns(2)
dis_col2.subheader("Personal Statistics")
dis_col2.plotly_chart(fig_personal, use_container_width=True) dis_col2.plotly_chart(fig_personal, use_container_width=True)
dis_col3.subheader("Knowledge and Complexity")
dis_col2.plotly_chart(fig_knowledge, use_container_width=True) dis_col2.plotly_chart(fig_knowledge, use_container_width=True)
detail_expander = dis_col1.expander("Data Table") detail_expander = dis_col1.expander("Data Table")
detail_expander.dataframe(request_estimator.data_set) detail_expander.dataframe(request_estimator.data_set)
dis_col3.metric("Company Employees", request_estimator.nr_of_employees, delta=add_new_employee) write_metric(request_estimator, sub_col1, "Available Working Time", "personal_available_time")
dis_col3.metric("Service Desk Employees", request_estimator.service_desk_employees, delta=add_new_service_desk_employees) write_metric(request_estimator, sub_col2, "Service Desk Knowledge Level", "service_desk_knowledge_level")
dis_col3.metric("Nr of Applications", request_estimator.nr_of_applications, delta=add_new_applications) write_metric(request_estimator, sub_col1, "Systems Complexity", "systems_complexity", "inverse")
dis_col3.metric("Nr of open Service Requests", request_estimator.nr_of_open_service_requests) write_metric(request_estimator, sub_col2, "Documentation Level", "documentation_level")
dis_col3.metric("Nr of processed Service Requests", request_estimator.processed_service_requests)
if request_estimator.personal_available_time > 0: write_metric(request_estimator, dis_col3, "Company Employees", "nr_of_employees")
st.info(f"Your team has available time of {request_estimator.personal_available_time:.0f} hours.") write_metric(request_estimator, dis_col3, "Service Desk Employees", "service_desk_employees")
write_metric(request_estimator, dis_col3, "Nr of Applications", "nr_of_applications")
write_metric(request_estimator, dis_col3, "Nr of open Service Requests", "nr_of_open_service_requests", "inverse")
write_metric(request_estimator, dis_col3, "Nr of processed Service Requests", "processed_service_requests")
else: else:
st.error("Your team has no available time. You are in the cycle of death! Hire new Guys!!!!") st.write("No data available yet! Push the button to process the data.")
else:
form.write(request_estimator.data_set)
st.divider() st.divider()