Update app.py
Browse files
app.py
CHANGED
@@ -1,38 +1,89 @@
|
|
1 |
import streamlit as st
|
2 |
import pandas as pd
|
3 |
-
from
|
4 |
-
import
|
5 |
|
6 |
-
#
|
7 |
-
st.title("Duty Roster Generator")
|
8 |
-
uploaded_file = st.file_uploader("Upload your spreadsheet", type=["csv", "xlsx"])
|
|
|
|
|
|
|
|
|
9 |
|
10 |
if uploaded_file:
|
11 |
-
# Load the
|
12 |
if uploaded_file.name.endswith('.csv'):
|
13 |
df = pd.read_csv(uploaded_file)
|
14 |
else:
|
15 |
df = pd.read_excel(uploaded_file)
|
16 |
|
17 |
-
# Display
|
18 |
-
st.write("Personnel Availability Data:")
|
19 |
st.dataframe(df)
|
20 |
|
21 |
-
#
|
22 |
-
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
33 |
return roster
|
34 |
|
35 |
-
#
|
36 |
-
|
37 |
-
|
38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import streamlit as st
|
2 |
import pandas as pd
|
3 |
+
from collections import defaultdict
|
4 |
+
import plotly.express as px
|
5 |
|
6 |
+
# Title and file upload
|
7 |
+
st.title("Enhanced Duty Roster Generator with Role-Specific Assignments")
|
8 |
+
uploaded_file = st.file_uploader("Upload your availability spreadsheet with roles", type=["csv", "xlsx"])
|
9 |
+
|
10 |
+
# Multi-shift configuration
|
11 |
+
shifts = st.multiselect("Select shifts for each day", ["Morning", "Afternoon", "Night"], ["Morning", "Afternoon"])
|
12 |
+
shift_requirements = {shift: st.number_input(f"Personnel required for {shift} shift", min_value=1, value=1) for shift in shifts}
|
13 |
|
14 |
if uploaded_file:
|
15 |
+
# Load the file
|
16 |
if uploaded_file.name.endswith('.csv'):
|
17 |
df = pd.read_csv(uploaded_file)
|
18 |
else:
|
19 |
df = pd.read_excel(uploaded_file)
|
20 |
|
21 |
+
# Display data and allow user to confirm roles
|
22 |
+
st.write("Personnel Availability and Roles Data:")
|
23 |
st.dataframe(df)
|
24 |
|
25 |
+
# Generate duty roster with roles
|
26 |
+
def generate_duty_roster_with_roles(data, shifts, shift_reqs):
|
27 |
+
roster = defaultdict(lambda: defaultdict(list))
|
28 |
+
|
29 |
+
for day in range(1, 31): # Example with 30 days
|
30 |
+
for shift in shifts:
|
31 |
+
role_counts = defaultdict(int)
|
32 |
+
assigned_today = 0
|
33 |
+
|
34 |
+
for _, row in data.iterrows():
|
35 |
+
name = row['Name']
|
36 |
+
role = row['Role']
|
37 |
+
unavailable_days = set(map(int, row['Unavailable Days'].split(',')))
|
38 |
+
|
39 |
+
if day not in unavailable_days and role_counts[role] < shift_reqs[shift]:
|
40 |
+
roster[day][shift].append((name, role))
|
41 |
+
role_counts[role] += 1
|
42 |
+
assigned_today += 1
|
43 |
+
|
44 |
+
if assigned_today == shift_reqs[shift]:
|
45 |
+
break
|
46 |
+
|
47 |
+
# If unable to meet required personnel, mark as unassigned
|
48 |
+
if assigned_today < shift_reqs[shift]:
|
49 |
+
roster[day][shift].append(("Unassigned", "N/A"))
|
50 |
+
|
51 |
return roster
|
52 |
|
53 |
+
# Generate and display the duty roster
|
54 |
+
duty_roster = generate_duty_roster_with_roles(df, shifts, shift_requirements)
|
55 |
+
|
56 |
+
# Display the roster with roles and shifts
|
57 |
+
st.write("Generated Duty Roster with Roles:")
|
58 |
+
for day, shifts in duty_roster.items():
|
59 |
+
st.write(f"Day {day}:")
|
60 |
+
for shift, personnel in shifts.items():
|
61 |
+
st.write(f"{shift} Shift: {', '.join([f'{p[0]} ({p[1]})' for p in personnel])}")
|
62 |
+
|
63 |
+
# Interactive Calendar Visualization
|
64 |
+
flattened_data = []
|
65 |
+
for day, shifts in duty_roster.items():
|
66 |
+
for shift, personnel in shifts.items():
|
67 |
+
for name, role in personnel:
|
68 |
+
flattened_data.append({"Day": day, "Shift": shift, "Personnel": name, "Role": role})
|
69 |
+
|
70 |
+
df_roster = pd.DataFrame(flattened_data)
|
71 |
+
fig = px.timeline(df_roster, x_start="Day", x_end="Day", y="Personnel", color="Shift", title="Duty Roster Calendar View")
|
72 |
+
st.plotly_chart(fig)
|
73 |
+
|
74 |
+
# Downloadable individual schedules
|
75 |
+
st.write("Download Individual Schedules:")
|
76 |
+
for person in df['Name'].unique():
|
77 |
+
person_schedule = df_roster[df_roster['Personnel'] == person]
|
78 |
+
person_csv = person_schedule.to_csv(index=False).encode('utf-8')
|
79 |
+
st.download_button(f"Download Schedule for {person}", data=person_csv, file_name=f"{person}_schedule.csv", mime="text/csv")
|
80 |
+
|
81 |
+
# Scheduling Insights and Analytics
|
82 |
+
st.write("Scheduling Insights:")
|
83 |
+
personnel_counts = df_roster.groupby("Personnel").size()
|
84 |
+
st.bar_chart(personnel_counts)
|
85 |
+
st.write(f"Total shifts assigned per person: {personnel_counts.to_dict()}")
|
86 |
+
|
87 |
+
st.write("Days with unassigned shifts:")
|
88 |
+
unassigned_days = df_roster[df_roster['Personnel'] == "Unassigned"]
|
89 |
+
st.write(unassigned_days[['Day', 'Shift']].drop_duplicates())
|