Compare commits
4 Commits
ffe45a2a5f
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 2f09371dd4 | |||
| 7a574dd089 | |||
| a4d1781186 | |||
| da84d81082 |
@@ -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
|
||||||
|
|||||||
BIN
graphics/kausaldiagramm.png
Normal file
BIN
graphics/kausaldiagramm.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 130 KiB |
@@ -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
|
||||||
|
|
||||||
@@ -188,21 +192,21 @@ class RequestLoadEstimator:
|
|||||||
self.nr_of_employees *
|
self.nr_of_employees *
|
||||||
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.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,
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user