HaLim commited on
Commit
ac5a7da
Β·
1 Parent(s): 4003592

configuration page

Browse files
Files changed (2) hide show
  1. app.py +46 -0
  2. config_page.py +379 -0
app.py ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Supply Roster Optimization Tool - Streamlit App
3
+ Simplified version with configuration and optimization results
4
+ """
5
+
6
+ import streamlit as st
7
+ import sys
8
+ import os
9
+
10
+ # Add src directory to path for imports
11
+ sys.path.append(os.path.join(os.path.dirname(__file__), 'src'))
12
+
13
+ # Set page config
14
+ st.set_page_config(
15
+ page_title="Supply Roster Optimization Tool",
16
+ page_icon="πŸ“¦",
17
+ layout="wide",
18
+ initial_sidebar_state="expanded"
19
+ )
20
+
21
+ # Sidebar navigation
22
+ st.sidebar.title("πŸ“¦ Supply Roster Tool")
23
+ st.sidebar.markdown("---")
24
+
25
+ # Navigation
26
+ page = st.sidebar.selectbox(
27
+ "Navigate to:",
28
+ ["βš™οΈ Configuration", "πŸ“Š Optimization Results"],
29
+ index=0
30
+ )
31
+
32
+ # Main app content
33
+ if page == "βš™οΈ Configuration":
34
+ # Import and render the config page
35
+ from config_page import render_config_page
36
+
37
+ st.title("πŸ“¦ Supply Roster Optimization Tool")
38
+ st.markdown("---")
39
+
40
+ render_config_page()
41
+
42
+ elif page == "πŸ“Š Optimization Results":
43
+ # Import and render the optimization page
44
+ from optimization_page import render_optimization_page
45
+
46
+ render_optimization_page()
config_page.py ADDED
@@ -0,0 +1,379 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Configuration Page for Supply Roster Optimization Tool
3
+ Contains all user input controls with default values from config
4
+ """
5
+
6
+ import streamlit as st
7
+ import datetime
8
+ import sys
9
+ import os
10
+
11
+ # Add src directory to path for imports
12
+ sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'src'))
13
+
14
+ def render_config_page():
15
+ """Render the configuration page with all user input controls"""
16
+
17
+ st.title("βš™οΈ Configuration")
18
+ st.markdown("---")
19
+ st.markdown("Configure optimization parameters and constraints for roster planning.")
20
+
21
+ # Initialize session state for all configuration values
22
+ initialize_session_state()
23
+
24
+ # Create tabs for better organization
25
+ tab1, tab2, tab3, tab4 = st.tabs(["πŸ“… Schedule", "πŸ‘₯ Workforce", "🏭 Operations", "πŸ’° Cost"])
26
+
27
+ with tab1:
28
+ render_schedule_config()
29
+
30
+ with tab2:
31
+ render_workforce_config()
32
+
33
+ with tab3:
34
+ render_operations_config()
35
+
36
+ with tab4:
37
+ render_cost_config()
38
+
39
+ # Save configuration button
40
+ st.markdown("---")
41
+ col1, col2, col3 = st.columns([1, 1, 1])
42
+ with col2:
43
+ if st.button("πŸ’Ύ Save Configuration", type="primary", use_container_width=True):
44
+ save_configuration()
45
+ st.success("βœ… Configuration saved successfully!")
46
+
47
+ def initialize_session_state():
48
+ """Initialize session state with default values from config"""
49
+
50
+ # Default values based on optimization_config.py
51
+ defaults = {
52
+ # Schedule configuration
53
+ 'start_date': datetime.date(2025, 7, 7),
54
+ 'end_date': datetime.date(2025, 7, 11),
55
+ 'schedule_type': 'daily',
56
+
57
+ # Shift configuration
58
+ 'evening_shift_mode': 'normal',
59
+ 'evening_shift_threshold': 0.9,
60
+
61
+ # Fixed staff configuration
62
+ 'fixed_staff_mode': 'priority',
63
+
64
+ # Payment configuration
65
+ 'payment_mode_shift_1': 'bulk',
66
+ 'payment_mode_shift_2': 'bulk',
67
+ 'payment_mode_shift_3': 'partial',
68
+
69
+ # Workforce limits
70
+ 'max_unicef_per_day': 8,
71
+ 'max_humanizer_per_day': 10,
72
+
73
+ # Operations
74
+ 'max_parallel_workers_long_line': 15,
75
+ 'max_parallel_workers_mini_load': 15,
76
+ 'max_hour_per_person_per_day': 14,
77
+
78
+ # Shift hours
79
+ 'max_hours_shift_1': 7.5,
80
+ 'max_hours_shift_2': 7.5,
81
+ 'max_hours_shift_3': 5.0,
82
+
83
+ # Line counts (will be loaded from data)
84
+ 'line_count_long_line': 6,
85
+ 'line_count_mini_load': 7,
86
+ }
87
+
88
+ # Initialize session state with defaults if not already set
89
+ for key, value in defaults.items():
90
+ if key not in st.session_state:
91
+ st.session_state[key] = value
92
+
93
+ def render_schedule_config():
94
+ """Render schedule configuration section"""
95
+ st.header("πŸ“… Schedule Configuration")
96
+
97
+ col1, col2 = st.columns(2)
98
+
99
+ with col1:
100
+ st.session_state.start_date = st.date_input(
101
+ "Start Date",
102
+ value=st.session_state.start_date,
103
+ help="Start date for the optimization period"
104
+ )
105
+
106
+ with col2:
107
+ st.session_state.end_date = st.date_input(
108
+ "End Date",
109
+ value=st.session_state.end_date,
110
+ help="End date for the optimization period"
111
+ )
112
+
113
+ # Validate date range
114
+ if st.session_state.start_date > st.session_state.end_date:
115
+ st.error("⚠️ Start date must be before or equal to end date!")
116
+
117
+ # Schedule type
118
+ st.session_state.schedule_type = st.selectbox(
119
+ "Schedule Type",
120
+ options=['daily', 'weekly'],
121
+ index=0 if st.session_state.schedule_type == 'daily' else 1,
122
+ help="Choose between daily or weekly scheduling"
123
+ )
124
+
125
+ # Evening shift configuration
126
+ st.subheader("πŸŒ™ Evening Shift Configuration")
127
+
128
+ st.session_state.evening_shift_mode = st.selectbox(
129
+ "Evening Shift Mode",
130
+ options=['normal', 'activate_evening', 'always_available'],
131
+ index=['normal', 'activate_evening', 'always_available'].index(st.session_state.evening_shift_mode),
132
+ help="""
133
+ - **Normal**: Only regular shift (1) and overtime shift (3)
134
+ - **Activate Evening**: Allow evening shift (2) when demand is high
135
+ - **Always Available**: Evening shift always available as option
136
+ """
137
+ )
138
+
139
+ if st.session_state.evening_shift_mode == 'activate_evening':
140
+ st.session_state.evening_shift_threshold = st.slider(
141
+ "Evening Shift Activation Threshold",
142
+ min_value=0.1,
143
+ max_value=1.0,
144
+ value=st.session_state.evening_shift_threshold,
145
+ step=0.1,
146
+ help="Activate evening shift if regular+overtime capacity < threshold of demand"
147
+ )
148
+
149
+ def render_workforce_config():
150
+ """Render workforce configuration section"""
151
+ st.header("πŸ‘₯ Workforce Configuration")
152
+
153
+ # Fixed staff constraint mode
154
+ st.session_state.fixed_staff_mode = st.selectbox(
155
+ "Fixed Staff Constraint Mode",
156
+ options=['mandatory', 'available', 'priority', 'none'],
157
+ index=['mandatory', 'available', 'priority', 'none'].index(st.session_state.fixed_staff_mode),
158
+ help="""
159
+ - **Mandatory**: Forces all fixed staff to work full hours every day
160
+ - **Available**: Staff available up to limits but not forced
161
+ - **Priority**: Fixed staff used first, then temporary staff (recommended)
162
+ - **None**: Purely demand-driven scheduling
163
+ """
164
+ )
165
+
166
+ # Workforce limits
167
+ st.subheader("πŸ‘¨β€πŸ’Ό Daily Workforce Limits")
168
+
169
+ col1, col2 = st.columns(2)
170
+
171
+ with col1:
172
+ st.session_state.max_unicef_per_day = st.number_input(
173
+ "Max UNICEF Fixed Term per Day",
174
+ min_value=1,
175
+ max_value=50,
176
+ value=st.session_state.max_unicef_per_day,
177
+ help="Maximum number of UNICEF fixed term employees per day"
178
+ )
179
+
180
+ with col2:
181
+ st.session_state.max_humanizer_per_day = st.number_input(
182
+ "Max Humanizer per Day",
183
+ min_value=1,
184
+ max_value=50,
185
+ value=st.session_state.max_humanizer_per_day,
186
+ help="Maximum number of Humanizer employees per day"
187
+ )
188
+
189
+ # Working hours configuration
190
+ st.subheader("⏰ Working Hours Configuration")
191
+
192
+ st.session_state.max_hour_per_person_per_day = st.number_input(
193
+ "Max Hours per Person per Day",
194
+ min_value=1,
195
+ max_value=24,
196
+ value=st.session_state.max_hour_per_person_per_day,
197
+ help="Legal maximum working hours per person per day"
198
+ )
199
+
200
+ col1, col2, col3 = st.columns(3)
201
+
202
+ with col1:
203
+ st.session_state.max_hours_shift_1 = st.number_input(
204
+ "Max Hours - Shift 1 (Regular)",
205
+ min_value=1.0,
206
+ max_value=12.0,
207
+ value=st.session_state.max_hours_shift_1,
208
+ step=0.5,
209
+ help="Maximum hours per person for regular shift"
210
+ )
211
+
212
+ with col2:
213
+ st.session_state.max_hours_shift_2 = st.number_input(
214
+ "Max Hours - Shift 2 (Evening)",
215
+ min_value=1.0,
216
+ max_value=12.0,
217
+ value=st.session_state.max_hours_shift_2,
218
+ step=0.5,
219
+ help="Maximum hours per person for evening shift"
220
+ )
221
+
222
+ with col3:
223
+ st.session_state.max_hours_shift_3 = st.number_input(
224
+ "Max Hours - Shift 3 (Overtime)",
225
+ min_value=1.0,
226
+ max_value=12.0,
227
+ value=st.session_state.max_hours_shift_3,
228
+ step=0.5,
229
+ help="Maximum hours per person for overtime shift"
230
+ )
231
+
232
+ def render_operations_config():
233
+ """Render operations configuration section"""
234
+ st.header("🏭 Operations Configuration")
235
+
236
+ # Line configuration
237
+ st.subheader("πŸ“¦ Production Line Configuration")
238
+
239
+ col1, col2 = st.columns(2)
240
+
241
+ with col1:
242
+ st.session_state.line_count_long_line = st.number_input(
243
+ "Number of Long Lines",
244
+ min_value=1,
245
+ max_value=20,
246
+ value=st.session_state.line_count_long_line,
247
+ help="Number of long line production lines available"
248
+ )
249
+
250
+ st.session_state.max_parallel_workers_long_line = st.number_input(
251
+ "Max Workers per Long Line",
252
+ min_value=1,
253
+ max_value=50,
254
+ value=st.session_state.max_parallel_workers_long_line,
255
+ help="Maximum number of workers that can work simultaneously on a long line"
256
+ )
257
+
258
+ with col2:
259
+ st.session_state.line_count_mini_load = st.number_input(
260
+ "Number of Mini Load Lines",
261
+ min_value=1,
262
+ max_value=20,
263
+ value=st.session_state.line_count_mini_load,
264
+ help="Number of mini load production lines available"
265
+ )
266
+
267
+ st.session_state.max_parallel_workers_mini_load = st.number_input(
268
+ "Max Workers per Mini Load Line",
269
+ min_value=1,
270
+ max_value=50,
271
+ value=st.session_state.max_parallel_workers_mini_load,
272
+ help="Maximum number of workers that can work simultaneously on a mini load line"
273
+ )
274
+
275
+ def render_cost_config():
276
+ """Render cost configuration section"""
277
+ st.header("πŸ’° Cost Configuration")
278
+
279
+ # Payment mode configuration
280
+ st.subheader("πŸ’³ Payment Mode Configuration")
281
+
282
+ st.markdown("""
283
+ **Payment Modes:**
284
+ - **Bulk**: If employee works any hours in shift, pay for full shift hours
285
+ - **Partial**: Pay only for actual hours worked
286
+ """)
287
+
288
+ col1, col2, col3 = st.columns(3)
289
+
290
+ with col1:
291
+ st.session_state.payment_mode_shift_1 = st.selectbox(
292
+ "Shift 1 (Regular) Payment",
293
+ options=['bulk', 'partial'],
294
+ index=0 if st.session_state.payment_mode_shift_1 == 'bulk' else 1,
295
+ help="Payment mode for regular shift"
296
+ )
297
+
298
+ with col2:
299
+ st.session_state.payment_mode_shift_2 = st.selectbox(
300
+ "Shift 2 (Evening) Payment",
301
+ options=['bulk', 'partial'],
302
+ index=0 if st.session_state.payment_mode_shift_2 == 'bulk' else 1,
303
+ help="Payment mode for evening shift"
304
+ )
305
+
306
+ with col3:
307
+ st.session_state.payment_mode_shift_3 = st.selectbox(
308
+ "Shift 3 (Overtime) Payment",
309
+ options=['bulk', 'partial'],
310
+ index=0 if st.session_state.payment_mode_shift_3 == 'bulk' else 1,
311
+ help="Payment mode for overtime shift"
312
+ )
313
+
314
+ # Cost information display (read-only from config)
315
+ st.subheader("πŸ’΅ Hourly Rates (From Configuration)")
316
+
317
+ # Display current cost configuration
318
+ cost_info = """
319
+ **UNICEF Fixed Term:**
320
+ - Shift 1 (Regular): €43.27/hour
321
+ - Shift 2 (Evening): €43.27/hour
322
+ - Shift 3 (Overtime): €64.91/hour
323
+
324
+ **Humanizer:**
325
+ - Shift 1 (Regular): €27.94/hour
326
+ - Shift 2 (Evening): €27.94/hour
327
+ - Shift 3 (Overtime): €41.91/hour
328
+ """
329
+
330
+ st.info(cost_info)
331
+
332
+ def save_configuration():
333
+ """Save current configuration to session state and potentially to file"""
334
+
335
+ # Create configuration dictionary
336
+ config = {
337
+ 'date_range': {
338
+ 'start_date': st.session_state.start_date,
339
+ 'end_date': st.session_state.end_date,
340
+ },
341
+ 'schedule_type': st.session_state.schedule_type,
342
+ 'evening_shift_mode': st.session_state.evening_shift_mode,
343
+ 'evening_shift_threshold': st.session_state.evening_shift_threshold,
344
+ 'fixed_staff_mode': st.session_state.fixed_staff_mode,
345
+ 'payment_mode_config': {
346
+ 1: st.session_state.payment_mode_shift_1,
347
+ 2: st.session_state.payment_mode_shift_2,
348
+ 3: st.session_state.payment_mode_shift_3,
349
+ },
350
+ 'workforce_limits': {
351
+ 'max_unicef_per_day': st.session_state.max_unicef_per_day,
352
+ 'max_humanizer_per_day': st.session_state.max_humanizer_per_day,
353
+ },
354
+ 'working_hours': {
355
+ 'max_hour_per_person_per_day': st.session_state.max_hour_per_person_per_day,
356
+ 'max_hours_per_shift': {
357
+ 1: st.session_state.max_hours_shift_1,
358
+ 2: st.session_state.max_hours_shift_2,
359
+ 3: st.session_state.max_hours_shift_3,
360
+ }
361
+ },
362
+ 'operations': {
363
+ 'line_counts': {
364
+ 'long_line': st.session_state.line_count_long_line,
365
+ 'mini_load': st.session_state.line_count_mini_load,
366
+ },
367
+ 'max_parallel_workers': {
368
+ 6: st.session_state.max_parallel_workers_long_line, # long line id
369
+ 7: st.session_state.max_parallel_workers_mini_load, # mini load id
370
+ }
371
+ }
372
+ }
373
+
374
+ # Store in session state for other pages to access
375
+ st.session_state.optimization_config = config
376
+
377
+ # Display configuration summary
378
+ with st.expander("πŸ“‹ Configuration Summary", expanded=False):
379
+ st.json(config)