HaLim commited on
Commit
de19c07
·
1 Parent(s): 131af7c

add configuration class

Browse files
config_page.py CHANGED
@@ -7,6 +7,8 @@ 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'))
@@ -78,7 +80,8 @@ def initialize_session_state():
78
  MAX_HOUR_PER_PERSON_PER_DAY, MAX_HOUR_PER_SHIFT_PER_PERSON,
79
  MAX_PARALLEL_WORKERS, COST_LIST_PER_EMP_SHIFT,
80
  PAYMENT_MODE_CONFIG, LINE_CNT_PER_TYPE,
81
- MAX_EMPLOYEE_PER_TYPE_ON_DAY, start_date, end_date
 
82
  )
83
 
84
  # Get the actual computed default values from optimization_config.py
@@ -96,35 +99,35 @@ def initialize_session_state():
96
  'fixed_staff_mode': FIXED_STAFF_CONSTRAINT_MODE,
97
 
98
  # Payment configuration - from optimization_config.py
99
- 'payment_mode_shift_1': PAYMENT_MODE_CONFIG.get(1),
100
- 'payment_mode_shift_2': PAYMENT_MODE_CONFIG.get(2),
101
- 'payment_mode_shift_3': PAYMENT_MODE_CONFIG.get(3),
102
 
103
  # Working hours - from optimization_config.py
104
  'max_hour_per_person_per_day': MAX_HOUR_PER_PERSON_PER_DAY,
105
- 'max_hours_shift_1': MAX_HOUR_PER_SHIFT_PER_PERSON.get(1),
106
- 'max_hours_shift_2': MAX_HOUR_PER_SHIFT_PER_PERSON.get(2),
107
- 'max_hours_shift_3': MAX_HOUR_PER_SHIFT_PER_PERSON.get(3),
108
 
109
  # Operations - from optimization_config.py
110
- 'max_parallel_workers_long_line': MAX_PARALLEL_WORKERS.get(6),
111
- 'max_parallel_workers_mini_load': MAX_PARALLEL_WORKERS.get(7),
112
 
113
  # Workforce limits - from optimization_config.py (computed values)
114
  'max_unicef_per_day': list(MAX_EMPLOYEE_PER_TYPE_ON_DAY.get("UNICEF Fixed term", {}).values())[0] if MAX_EMPLOYEE_PER_TYPE_ON_DAY.get("UNICEF Fixed term") else 8,
115
  'max_humanizer_per_day': list(MAX_EMPLOYEE_PER_TYPE_ON_DAY.get("Humanizer", {}).values())[0] if MAX_EMPLOYEE_PER_TYPE_ON_DAY.get("Humanizer") else 10,
116
 
117
  # Line counts - from optimization_config.py (data-driven)
118
- 'line_count_long_line': LINE_CNT_PER_TYPE.get(6),
119
- 'line_count_mini_load': LINE_CNT_PER_TYPE.get(7),
120
 
121
  # Cost rates - from optimization_config.py (computed or default values)
122
- 'unicef_rate_shift_1': COST_LIST_PER_EMP_SHIFT.get("UNICEF Fixed term", {}).get(1),
123
- 'unicef_rate_shift_2': COST_LIST_PER_EMP_SHIFT.get("UNICEF Fixed term", {}).get(2),
124
- 'unicef_rate_shift_3': COST_LIST_PER_EMP_SHIFT.get("UNICEF Fixed term", {}).get(3),
125
- 'humanizer_rate_shift_1': COST_LIST_PER_EMP_SHIFT.get("Humanizer", {}).get(1),
126
- 'humanizer_rate_shift_2': COST_LIST_PER_EMP_SHIFT.get("Humanizer", {}).get(2),
127
- 'humanizer_rate_shift_3': COST_LIST_PER_EMP_SHIFT.get("Humanizer", {}).get(3),
128
  }
129
 
130
  except Exception as e:
@@ -456,8 +459,8 @@ def render_data_selection_config():
456
 
457
  # Shifts selection
458
  st.subheader("🕐 Shifts")
459
- available_shifts = [1, 2, 3]
460
- shift_names = {1: "Regular", 2: "Evening", 3: "Overtime"}
461
 
462
  if 'selected_shifts' not in st.session_state:
463
  st.session_state.selected_shifts = available_shifts
@@ -473,8 +476,8 @@ def render_data_selection_config():
473
 
474
  # Production lines selection
475
  st.subheader("🏭 Production Lines")
476
- available_lines = [6, 7]
477
- line_names = {6: "Long Line", 7: "Mini Load"}
478
 
479
  if 'selected_lines' not in st.session_state:
480
  st.session_state.selected_lines = available_lines
@@ -510,9 +513,9 @@ def save_configuration():
510
  'evening_shift_threshold': st.session_state.evening_shift_threshold,
511
  'fixed_staff_mode': st.session_state.fixed_staff_mode,
512
  'payment_mode_config': {
513
- 1: st.session_state.payment_mode_shift_1,
514
- 2: st.session_state.payment_mode_shift_2,
515
- 3: st.session_state.payment_mode_shift_3,
516
  },
517
  'workforce_limits': {
518
  'max_unicef_per_day': st.session_state.max_unicef_per_day,
@@ -521,31 +524,31 @@ def save_configuration():
521
  'working_hours': {
522
  'max_hour_per_person_per_day': st.session_state.max_hour_per_person_per_day,
523
  'max_hours_per_shift': {
524
- 1: st.session_state.max_hours_shift_1,
525
- 2: st.session_state.max_hours_shift_2,
526
- 3: st.session_state.max_hours_shift_3,
527
  }
528
  },
529
  'operations': {
530
  'line_counts': {
531
- 6: st.session_state.line_count_long_line, # Use line IDs directly
532
- 7: st.session_state.line_count_mini_load,
533
  },
534
  'max_parallel_workers': {
535
- 6: st.session_state.max_parallel_workers_long_line, # long line id
536
- 7: st.session_state.max_parallel_workers_mini_load, # mini load id
537
  }
538
  },
539
  'cost_rates': {
540
  'UNICEF Fixed term': {
541
- 1: st.session_state.unicef_rate_shift_1,
542
- 2: st.session_state.unicef_rate_shift_2,
543
- 3: st.session_state.unicef_rate_shift_3,
544
  },
545
  'Humanizer': {
546
- 1: st.session_state.humanizer_rate_shift_1,
547
- 2: st.session_state.humanizer_rate_shift_2,
548
- 3: st.session_state.humanizer_rate_shift_3,
549
  }
550
  },
551
  'data_selection': {
@@ -603,11 +606,11 @@ def display_user_friendly_summary(config):
603
  st.subheader("🏭 Operations Settings")
604
  col1, col2 = st.columns(2)
605
  with col1:
606
- st.write(f"**Long Lines Available:** {config['operations']['line_counts'][6]} lines")
607
- st.write(f"**Mini Load Lines Available:** {config['operations']['line_counts'][7]} lines")
608
  with col2:
609
- st.write(f"**Max Workers per Long Line:** {config['operations']['max_parallel_workers'][6]} people")
610
- st.write(f"**Max Workers per Mini Load Line:** {config['operations']['max_parallel_workers'][7]} people")
611
 
612
  # Cost Settings
613
  st.subheader("💰 Cost Settings")
@@ -616,15 +619,15 @@ def display_user_friendly_summary(config):
616
  col1, col2 = st.columns(2)
617
  with col1:
618
  st.write("*UNICEF Fixed Term Staff:*")
619
- st.write(f"• Regular Shift: €{config['cost_rates']['UNICEF Fixed term'][1]:.2f}/hour")
620
- st.write(f"• Evening Shift: €{config['cost_rates']['UNICEF Fixed term'][2]:.2f}/hour")
621
- st.write(f"• Overtime Shift: €{config['cost_rates']['UNICEF Fixed term'][3]:.2f}/hour")
622
 
623
  with col2:
624
  st.write("*Humanizer Staff:*")
625
- st.write(f"• Regular Shift: €{config['cost_rates']['Humanizer'][1]:.2f}/hour")
626
- st.write(f"• Evening Shift: €{config['cost_rates']['Humanizer'][2]:.2f}/hour")
627
- st.write(f"• Overtime Shift: €{config['cost_rates']['Humanizer'][3]:.2f}/hour")
628
 
629
  # Payment Settings
630
  st.write("**Payment Modes:**")
@@ -635,15 +638,15 @@ def display_user_friendly_summary(config):
635
 
636
  col1, col2, col3 = st.columns(3)
637
  with col1:
638
- mode = config['payment_mode_config'][1]
639
  st.write(f"• **Regular Shift:** {mode.title()}")
640
  st.caption(payment_descriptions[mode])
641
  with col2:
642
- mode = config['payment_mode_config'][2]
643
  st.write(f"• **Evening Shift:** {mode.title()}")
644
  st.caption(payment_descriptions[mode])
645
  with col3:
646
- mode = config['payment_mode_config'][3]
647
  st.write(f"• **Overtime Shift:** {mode.title()}")
648
  st.caption(payment_descriptions[mode])
649
 
@@ -659,14 +662,14 @@ def display_user_friendly_summary(config):
659
 
660
  with col2:
661
  shifts = config['data_selection']['selected_shifts']
662
- shift_names = {1: "Regular", 2: "Evening", 3: "Overtime"}
663
  st.write(f"**Shifts:** {len(shifts)} selected")
664
  for shift in shifts:
665
  st.write(f"• Shift {shift} ({shift_names.get(shift, 'Unknown')})")
666
 
667
  with col3:
668
  lines = config['data_selection']['selected_lines']
669
- line_names = {6: "Long Line", 7: "Mini Load"}
670
  st.write(f"**Production Lines:** {len(lines)} selected")
671
  for line in lines:
672
  st.write(f"• Line {line} ({line_names.get(line, 'Unknown')})")
@@ -684,7 +687,7 @@ def display_user_friendly_summary(config):
684
  st.metric("Max Daily Staff", f"{total_staff} people")
685
 
686
  with col3:
687
- total_lines = config['operations']['line_counts'][6] + config['operations']['line_counts'][7]
688
  st.metric("Production Lines", f"{total_lines} lines")
689
 
690
  with col4:
 
7
  import datetime
8
  import sys
9
  import os
10
+ from config import optimization_config
11
+ from src.config.constants import ShiftType, LineType
12
 
13
  # Add src directory to path for imports
14
  sys.path.append(os.path.join(os.path.dirname(__file__), '..', 'src'))
 
80
  MAX_HOUR_PER_PERSON_PER_DAY, MAX_HOUR_PER_SHIFT_PER_PERSON,
81
  MAX_PARALLEL_WORKERS, COST_LIST_PER_EMP_SHIFT,
82
  PAYMENT_MODE_CONFIG, LINE_CNT_PER_TYPE,
83
+ MAX_EMPLOYEE_PER_TYPE_ON_DAY, start_date, end_date,
84
+ shift_code_to_name
85
  )
86
 
87
  # Get the actual computed default values from optimization_config.py
 
99
  'fixed_staff_mode': FIXED_STAFF_CONSTRAINT_MODE,
100
 
101
  # Payment configuration - from optimization_config.py
102
+ 'payment_mode_shift_1': PAYMENT_MODE_CONFIG.get(ShiftType.REGULAR),
103
+ 'payment_mode_shift_2': PAYMENT_MODE_CONFIG.get(ShiftType.EVENING),
104
+ 'payment_mode_shift_3': PAYMENT_MODE_CONFIG.get(ShiftType.OVERTIME),
105
 
106
  # Working hours - from optimization_config.py
107
  'max_hour_per_person_per_day': MAX_HOUR_PER_PERSON_PER_DAY,
108
+ 'max_hours_shift_1': MAX_HOUR_PER_SHIFT_PER_PERSON.get(ShiftType.REGULAR),
109
+ 'max_hours_shift_2': MAX_HOUR_PER_SHIFT_PER_PERSON.get(ShiftType.EVENING),
110
+ 'max_hours_shift_3': MAX_HOUR_PER_SHIFT_PER_PERSON.get(ShiftType.OVERTIME),
111
 
112
  # Operations - from optimization_config.py
113
+ 'max_parallel_workers_long_line': MAX_PARALLEL_WORKERS.get(LineType.LONG_LINE),
114
+ 'max_parallel_workers_mini_load': MAX_PARALLEL_WORKERS.get(LineType.MINI_LOAD),
115
 
116
  # Workforce limits - from optimization_config.py (computed values)
117
  'max_unicef_per_day': list(MAX_EMPLOYEE_PER_TYPE_ON_DAY.get("UNICEF Fixed term", {}).values())[0] if MAX_EMPLOYEE_PER_TYPE_ON_DAY.get("UNICEF Fixed term") else 8,
118
  'max_humanizer_per_day': list(MAX_EMPLOYEE_PER_TYPE_ON_DAY.get("Humanizer", {}).values())[0] if MAX_EMPLOYEE_PER_TYPE_ON_DAY.get("Humanizer") else 10,
119
 
120
  # Line counts - from optimization_config.py (data-driven)
121
+ 'line_count_long_line': LINE_CNT_PER_TYPE.get(LineType.LONG_LINE),
122
+ 'line_count_mini_load': LINE_CNT_PER_TYPE.get(LineType.MINI_LOAD),
123
 
124
  # Cost rates - from optimization_config.py (computed or default values)
125
+ 'unicef_rate_shift_1': COST_LIST_PER_EMP_SHIFT.get("UNICEF Fixed term", {}).get(ShiftType.REGULAR),
126
+ 'unicef_rate_shift_2': COST_LIST_PER_EMP_SHIFT.get("UNICEF Fixed term", {}).get(ShiftType.EVENING),
127
+ 'unicef_rate_shift_3': COST_LIST_PER_EMP_SHIFT.get("UNICEF Fixed term", {}).get(ShiftType.OVERTIME),
128
+ 'humanizer_rate_shift_1': COST_LIST_PER_EMP_SHIFT.get("Humanizer", {}).get(ShiftType.REGULAR),
129
+ 'humanizer_rate_shift_2': COST_LIST_PER_EMP_SHIFT.get("Humanizer", {}).get(ShiftType.EVENING),
130
+ 'humanizer_rate_shift_3': COST_LIST_PER_EMP_SHIFT.get("Humanizer", {}).get(ShiftType.OVERTIME),
131
  }
132
 
133
  except Exception as e:
 
459
 
460
  # Shifts selection
461
  st.subheader("🕐 Shifts")
462
+ available_shifts = list(optimization_config.shift_code_to_name().keys())
463
+ shift_names = optimization_config.shift_code_to_name()
464
 
465
  if 'selected_shifts' not in st.session_state:
466
  st.session_state.selected_shifts = available_shifts
 
476
 
477
  # Production lines selection
478
  st.subheader("🏭 Production Lines")
479
+ available_lines = list(optimization_config.line_code_to_name().keys())
480
+ line_names = optimization_config.line_code_to_name()
481
 
482
  if 'selected_lines' not in st.session_state:
483
  st.session_state.selected_lines = available_lines
 
513
  'evening_shift_threshold': st.session_state.evening_shift_threshold,
514
  'fixed_staff_mode': st.session_state.fixed_staff_mode,
515
  'payment_mode_config': {
516
+ ShiftType.REGULAR: st.session_state.payment_mode_shift_1,
517
+ ShiftType.EVENING: st.session_state.payment_mode_shift_2,
518
+ ShiftType.OVERTIME: st.session_state.payment_mode_shift_3,
519
  },
520
  'workforce_limits': {
521
  'max_unicef_per_day': st.session_state.max_unicef_per_day,
 
524
  'working_hours': {
525
  'max_hour_per_person_per_day': st.session_state.max_hour_per_person_per_day,
526
  'max_hours_per_shift': {
527
+ ShiftType.REGULAR: st.session_state.max_hours_shift_1,
528
+ ShiftType.EVENING: st.session_state.max_hours_shift_2,
529
+ ShiftType.OVERTIME: st.session_state.max_hours_shift_3,
530
  }
531
  },
532
  'operations': {
533
  'line_counts': {
534
+ LineType.LONG_LINE: st.session_state.line_count_long_line,
535
+ LineType.MINI_LOAD: st.session_state.line_count_mini_load,
536
  },
537
  'max_parallel_workers': {
538
+ LineType.LONG_LINE: st.session_state.max_parallel_workers_long_line,
539
+ LineType.MINI_LOAD: st.session_state.max_parallel_workers_mini_load,
540
  }
541
  },
542
  'cost_rates': {
543
  'UNICEF Fixed term': {
544
+ ShiftType.REGULAR: st.session_state.unicef_rate_shift_1,
545
+ ShiftType.EVENING: st.session_state.unicef_rate_shift_2,
546
+ ShiftType.OVERTIME: st.session_state.unicef_rate_shift_3,
547
  },
548
  'Humanizer': {
549
+ ShiftType.REGULAR: st.session_state.humanizer_rate_shift_1,
550
+ ShiftType.EVENING: st.session_state.humanizer_rate_shift_2,
551
+ ShiftType.OVERTIME: st.session_state.humanizer_rate_shift_3,
552
  }
553
  },
554
  'data_selection': {
 
606
  st.subheader("🏭 Operations Settings")
607
  col1, col2 = st.columns(2)
608
  with col1:
609
+ st.write(f"**Long Lines Available:** {config['operations']['line_counts'][LineType.LONG_LINE]} lines")
610
+ st.write(f"**Mini Load Lines Available:** {config['operations']['line_counts'][LineType.MINI_LOAD]} lines")
611
  with col2:
612
+ st.write(f"**Max Workers per Long Line:** {config['operations']['max_parallel_workers'][LineType.LONG_LINE]} people")
613
+ st.write(f"**Max Workers per Mini Load Line:** {config['operations']['max_parallel_workers'][LineType.MINI_LOAD]} people")
614
 
615
  # Cost Settings
616
  st.subheader("💰 Cost Settings")
 
619
  col1, col2 = st.columns(2)
620
  with col1:
621
  st.write("*UNICEF Fixed Term Staff:*")
622
+ st.write(f"• Regular Shift: €{config['cost_rates']['UNICEF Fixed term'][ShiftType.REGULAR]:.2f}/hour")
623
+ st.write(f"• Evening Shift: €{config['cost_rates']['UNICEF Fixed term'][ShiftType.EVENING]:.2f}/hour")
624
+ st.write(f"• Overtime Shift: €{config['cost_rates']['UNICEF Fixed term'][ShiftType.OVERTIME]:.2f}/hour")
625
 
626
  with col2:
627
  st.write("*Humanizer Staff:*")
628
+ st.write(f"• Regular Shift: €{config['cost_rates']['Humanizer'][ShiftType.REGULAR]:.2f}/hour")
629
+ st.write(f"• Evening Shift: €{config['cost_rates']['Humanizer'][ShiftType.EVENING]:.2f}/hour")
630
+ st.write(f"• Overtime Shift: €{config['cost_rates']['Humanizer'][ShiftType.OVERTIME]:.2f}/hour")
631
 
632
  # Payment Settings
633
  st.write("**Payment Modes:**")
 
638
 
639
  col1, col2, col3 = st.columns(3)
640
  with col1:
641
+ mode = config['payment_mode_config'][ShiftType.REGULAR]
642
  st.write(f"• **Regular Shift:** {mode.title()}")
643
  st.caption(payment_descriptions[mode])
644
  with col2:
645
+ mode = config['payment_mode_config'][ShiftType.EVENING]
646
  st.write(f"• **Evening Shift:** {mode.title()}")
647
  st.caption(payment_descriptions[mode])
648
  with col3:
649
+ mode = config['payment_mode_config'][ShiftType.OVERTIME]
650
  st.write(f"• **Overtime Shift:** {mode.title()}")
651
  st.caption(payment_descriptions[mode])
652
 
 
662
 
663
  with col2:
664
  shifts = config['data_selection']['selected_shifts']
665
+ shift_names = ShiftType.get_all_names()
666
  st.write(f"**Shifts:** {len(shifts)} selected")
667
  for shift in shifts:
668
  st.write(f"• Shift {shift} ({shift_names.get(shift, 'Unknown')})")
669
 
670
  with col3:
671
  lines = config['data_selection']['selected_lines']
672
+ line_names = LineType.get_all_names()
673
  st.write(f"**Production Lines:** {len(lines)} selected")
674
  for line in lines:
675
  st.write(f"• Line {line} ({line_names.get(line, 'Unknown')})")
 
687
  st.metric("Max Daily Staff", f"{total_staff} people")
688
 
689
  with col3:
690
+ total_lines = config['operations']['line_counts'][LineType.LONG_LINE] + config['operations']['line_counts'][LineType.MINI_LOAD]
691
  st.metric("Production Lines", f"{total_lines} lines")
692
 
693
  with col4:
src/config/optimization_config.py CHANGED
@@ -3,6 +3,7 @@ import src.etl.transform as transformed_data
3
  import datetime
4
  from datetime import timedelta
5
  import src.etl.extract as extract
 
6
 
7
  # Re-import all the packages
8
  import importlib
@@ -98,8 +99,8 @@ def get_active_shift_list():
98
  all_shifts = get_shift_list()
99
 
100
  if EVENING_SHIFT_MODE == "normal":
101
- # Only regular (1) and overtime (3) shifts - NO evening shift
102
- active_shifts = [s for s in all_shifts if s in [1, 3]]
103
  print(f"[SHIFT MODE] Normal mode: Using shifts {active_shifts} (Regular + Overtime only, NO evening)")
104
 
105
  elif EVENING_SHIFT_MODE == "activate_evening":
@@ -114,7 +115,7 @@ def get_active_shift_list():
114
 
115
  else:
116
  # Default to normal mode
117
- active_shifts = [s for s in all_shifts if s in [1, 3]]
118
  print(f"[SHIFT MODE] Unknown mode '{EVENING_SHIFT_MODE}', defaulting to normal: {active_shifts}")
119
 
120
  return active_shifts
@@ -150,10 +151,10 @@ def get_kit_line_match():
150
 
151
  # Create line name to ID mapping
152
  line_name_to_id = {
153
- "long line": 6,
154
- "mini load": 7,
155
- "Long_line": 6, # Alternative naming
156
- "Mini_load": 7, # Alternative naming
157
  }
158
 
159
  # Convert string line names to numeric IDs
@@ -167,13 +168,13 @@ def get_kit_line_match():
167
  else:
168
  print(f"Warning: Unknown line type '{line_name}' for kit {kit}")
169
  # Default to long line if unknown
170
- converted_dict[kit] = 6
171
  elif isinstance(line_name, (int, float)) and not pd.isna(line_name):
172
  # Already numeric
173
  converted_dict[kit] = int(line_name)
174
  else:
175
  # Missing or empty line type - default to long line
176
- converted_dict[kit] = 6
177
 
178
  return converted_dict
179
 
@@ -231,24 +232,14 @@ def get_cost_list_per_emp_shift():
231
 
232
  print(f"Loading default cost values")
233
  # Default hourly rates - Important: multiple employment types with different costs
234
- # Shift 1 = normal, 2 = evening, 3 = overtime
235
- return {"UNICEF Fixed term":{1:43.27,2:43.27,3:64.91},"Humanizer":{1:27.94,2:27.94,3:41.91}}
236
 
237
  def shift_code_to_name():
238
- shift_code_to_name_dict = {
239
- 1: "Regular",
240
- 2: "Evening",
241
- 3: "Overtime"
242
- }
243
- return shift_code_to_name_dict
244
 
245
  def line_code_to_name():
246
  """Convert line type IDs to readable names"""
247
- line_code_to_name_dict = {
248
- 6: "Long Line",
249
- 7: "Mini Load"
250
- }
251
- return line_code_to_name_dict
252
 
253
  COST_LIST_PER_EMP_SHIFT = get_cost_list_per_emp_shift()
254
  # print("cost list per emp shift",COST_LIST_PER_EMP_SHIFT)
@@ -339,7 +330,7 @@ print("max employee per type on day",MAX_EMPLOYEE_PER_TYPE_ON_DAY)
339
  # available employee but for fixed in shift 1, it is mandatory employment
340
 
341
  MAX_HOUR_PER_PERSON_PER_DAY = 14 # legal standard
342
- MAX_HOUR_PER_SHIFT_PER_PERSON = {1: 7.5, 2: 7.5, 3: 5} #1 = normal, 2 = evening, 3 = overtime
343
  def get_per_product_speed():
344
  try:
345
  # Try to get from streamlit session state (from config page)
@@ -378,10 +369,7 @@ def get_kit_hierarchy_data():
378
  KIT_LEVELS, KIT_DEPENDENCIES, PRODUCTION_PRIORITY_ORDER = get_kit_hierarchy_data()
379
  print(f"Kit Hierarchy loaded: {len(KIT_LEVELS)} kits, Priority order: {len(PRODUCTION_PRIORITY_ORDER)} items")
380
 
381
- MAX_PARALLEL_WORKERS = {
382
- 6: 15, # long line can have max 15 workers simultaneously
383
- 7: 15, # mini load can have max 15 workers simultaneously
384
- }
385
  # maximum number of workers that can work on a line at the same time
386
 
387
  DAILY_WEEKLY_SCHEDULE = "daily" # daily or weekly ,this needs to be implementedin in if F_x1_day is not None... F_x1_week is not None... also need to change x1 to Fixedstaff_first_shift
@@ -412,13 +400,8 @@ def get_payment_mode_config():
412
  print(f"Could not get payment mode config from streamlit session: {e}")
413
 
414
  # Default payment mode configuration
415
- # Shift 1: bulk, Shift 2: bulk (evening), Shift 3: partial (overtime)
416
  print(f"Loading default payment mode configuration")
417
- payment_mode_config = {
418
- 1: "bulk", # Regular shift - bulk payment
419
- 2: "bulk", # Evening shift - bulk payment
420
- 3: "partial" # Overtime shift - partial payment
421
- }
422
 
423
  return payment_mode_config
424
 
 
3
  import datetime
4
  from datetime import timedelta
5
  import src.etl.extract as extract
6
+ from src.config.constants import ShiftType, LineType, KitLevel, DefaultConfig
7
 
8
  # Re-import all the packages
9
  import importlib
 
99
  all_shifts = get_shift_list()
100
 
101
  if EVENING_SHIFT_MODE == "normal":
102
+ # Only regular and overtime shifts - NO evening shift
103
+ active_shifts = [s for s in all_shifts if s in ShiftType.REGULAR_AND_OVERTIME]
104
  print(f"[SHIFT MODE] Normal mode: Using shifts {active_shifts} (Regular + Overtime only, NO evening)")
105
 
106
  elif EVENING_SHIFT_MODE == "activate_evening":
 
115
 
116
  else:
117
  # Default to normal mode
118
+ active_shifts = [s for s in all_shifts if s in ShiftType.REGULAR_AND_OVERTIME]
119
  print(f"[SHIFT MODE] Unknown mode '{EVENING_SHIFT_MODE}', defaulting to normal: {active_shifts}")
120
 
121
  return active_shifts
 
151
 
152
  # Create line name to ID mapping
153
  line_name_to_id = {
154
+ "long line": LineType.LONG_LINE,
155
+ "mini load": LineType.MINI_LOAD,
156
+ "Long_line": LineType.LONG_LINE, # Alternative naming
157
+ "Mini_load": LineType.MINI_LOAD, # Alternative naming
158
  }
159
 
160
  # Convert string line names to numeric IDs
 
168
  else:
169
  print(f"Warning: Unknown line type '{line_name}' for kit {kit}")
170
  # Default to long line if unknown
171
+ converted_dict[kit] = LineType.LONG_LINE
172
  elif isinstance(line_name, (int, float)) and not pd.isna(line_name):
173
  # Already numeric
174
  converted_dict[kit] = int(line_name)
175
  else:
176
  # Missing or empty line type - default to long line
177
+ converted_dict[kit] = LineType.LONG_LINE
178
 
179
  return converted_dict
180
 
 
232
 
233
  print(f"Loading default cost values")
234
  # Default hourly rates - Important: multiple employment types with different costs
235
+ return DefaultConfig.DEFAULT_COST_RATES
 
236
 
237
  def shift_code_to_name():
238
+ return ShiftType.get_all_names()
 
 
 
 
 
239
 
240
  def line_code_to_name():
241
  """Convert line type IDs to readable names"""
242
+ return LineType.get_all_names()
 
 
 
 
243
 
244
  COST_LIST_PER_EMP_SHIFT = get_cost_list_per_emp_shift()
245
  # print("cost list per emp shift",COST_LIST_PER_EMP_SHIFT)
 
330
  # available employee but for fixed in shift 1, it is mandatory employment
331
 
332
  MAX_HOUR_PER_PERSON_PER_DAY = 14 # legal standard
333
+ MAX_HOUR_PER_SHIFT_PER_PERSON = DefaultConfig.MAX_HOUR_PER_SHIFT_PER_PERSON
334
  def get_per_product_speed():
335
  try:
336
  # Try to get from streamlit session state (from config page)
 
369
  KIT_LEVELS, KIT_DEPENDENCIES, PRODUCTION_PRIORITY_ORDER = get_kit_hierarchy_data()
370
  print(f"Kit Hierarchy loaded: {len(KIT_LEVELS)} kits, Priority order: {len(PRODUCTION_PRIORITY_ORDER)} items")
371
 
372
+ MAX_PARALLEL_WORKERS = DefaultConfig.MAX_PARALLEL_WORKERS
 
 
 
373
  # maximum number of workers that can work on a line at the same time
374
 
375
  DAILY_WEEKLY_SCHEDULE = "daily" # daily or weekly ,this needs to be implementedin in if F_x1_day is not None... F_x1_week is not None... also need to change x1 to Fixedstaff_first_shift
 
400
  print(f"Could not get payment mode config from streamlit session: {e}")
401
 
402
  # Default payment mode configuration
 
403
  print(f"Loading default payment mode configuration")
404
+ payment_mode_config = DefaultConfig.PAYMENT_MODE_CONFIG
 
 
 
 
405
 
406
  return payment_mode_config
407
 
src/models/optimizer_real.py CHANGED
@@ -9,6 +9,7 @@
9
 
10
  from ortools.linear_solver import pywraplp
11
  from math import ceil
 
12
 
13
  # ---- config import (프로젝트 경로에 맞춰 조정) ----
14
  from src.config.optimization_config import (
@@ -49,7 +50,7 @@ print("KIT_LINE_MATCH_DICT",KIT_LINE_MATCH_DICT)
49
 
50
  # 3) If specific product is not produced on specific date, set it to 0
51
  ACTIVE = {t: {p: 1 for p in PRODUCT_LIST} for t in DATE_SPAN}
52
- # 예: ACTIVE[2]['C'] = 0
53
 
54
 
55
  def build_lines():
@@ -89,7 +90,7 @@ def sort_products_by_hierarchy(product_list):
89
  print(f"[HIERARCHY] Production order: {len(sorted_products)} products")
90
  for i, p in enumerate(sorted_products[:10]): # Show first 10
91
  level = KIT_LEVELS.get(p, "unknown")
92
- level_name = {0: "prepack", 1: "subkit", 2: "master"}.get(level, "unknown")
93
  deps = KIT_DEPENDENCIES.get(p, [])
94
  print(f" {i+1}. {p} (level {level}={level_name}, deps: {len(deps)})")
95
 
@@ -103,10 +104,8 @@ def get_dependency_timing_weight(product):
103
  Calculate timing weight based on hierarchy level.
104
  Lower levels (prepacks) should be produced earlier.
105
  """
106
- level = KIT_LEVELS.get(product, 2) # Default to master level
107
- # Weight: prepack=0.1, subkit=0.5, master=1.0
108
- weights = {0: 0.1, 1: 0.5, 2: 1.0}
109
- return weights.get(level, 1.0)
110
 
111
  def solve_fixed_team_weekly():
112
  # --- Sets ---
@@ -148,7 +147,7 @@ def solve_fixed_team_weekly():
148
  total_demand = sum(DEMAND_DICTIONARY.get(p, 0) for p in P)
149
 
150
  # Calculate maximum capacity with regular + overtime shifts only
151
- regular_overtime_shifts = [s for s in S if s in [1, 3]] # Only shifts 1, 3 (regular + overtime)
152
  max_capacity = 0
153
 
154
  for p in P:
@@ -268,7 +267,7 @@ def solve_fixed_team_weekly():
268
 
269
  # 3) Product-line type compatibility + (optional) activity by day
270
  for p in P:
271
- req_lt = KIT_LINE_MATCH_DICT.get(p, 6) # Default to long line (6) if not found
272
  req_total = sum(TEAM_REQ_PER_PRODUCT[e][p] for e in E)
273
  for ell in L:
274
  allowed = (ell[0] == req_lt) and (req_total <= max_workers_line.get(ell[0], 1e9))
@@ -317,24 +316,24 @@ def solve_fixed_team_weekly():
317
  )
318
 
319
  # 7) Shift ordering constraints (only apply if shifts are available)
320
- # Evening shift (2) after regular shift (1)
321
- if 2 in S and 1 in S: # Only if both shifts are available
322
  for e in E:
323
  for t in D:
324
  solver.Add(
325
- solver.Sum(TEAM_REQ_PER_PRODUCT[e][p] * T[p, ell, 2, t] for p in P for ell in L)
326
  <=
327
- solver.Sum(TEAM_REQ_PER_PRODUCT[e][p] * T[p, ell, 1, t] for p in P for ell in L)
328
  )
329
 
330
- # Overtime shift (3) after regular shift (1)
331
- if 3 in S and 1 in S: # Only if both shifts are available
332
  for e in E:
333
  for t in D:
334
  solver.Add(
335
- solver.Sum(TEAM_REQ_PER_PRODUCT[e][p] * T[p, ell, 3, t] for p in P for ell in L)
336
  <=
337
- solver.Sum(TEAM_REQ_PER_PRODUCT[e][p] * T[p, ell, 1, t] for p in P for ell in L)
338
  )
339
 
340
  # 7.5) Bulk payment linking constraints are now handled inline in the cost calculation
@@ -371,7 +370,8 @@ def solve_fixed_team_weekly():
371
  # --- Solve ---
372
  status = solver.Solve()
373
  if status != pywraplp.Solver.OPTIMAL:
374
- print(f"No optimal solution. Status: {status} (2=INFEASIBLE)")
 
375
  # Debug hint:
376
  # solver.EnableOutput()
377
  # solver.ExportModelAsLpFile("model.lp")
@@ -433,12 +433,15 @@ def solve_fixed_team_weekly():
433
 
434
  print("\n--- Schedule (line, shift, day) ---")
435
  for row in schedule:
436
- print(f"D{row['day']} L{row['line_type_id']}-{row['line_idx']} S{row['shift']}: "
 
 
437
  f"{row['product']} T={row['run_hours']:.2f}h U={row['units']:.1f}")
438
 
439
  print("\n--- Implied headcount need (per type/shift/day) ---")
440
  for row in headcount:
441
- print(f"{row['emp_type']}, S{row['shift']}, D{row['day']}: "
 
442
  f"need={row['needed']} (avail {row['available']})")
443
 
444
  print("\n--- Total person-hours by type/day ---")
 
9
 
10
  from ortools.linear_solver import pywraplp
11
  from math import ceil
12
+ from src.config.constants import ShiftType, LineType, KitLevel
13
 
14
  # ---- config import (프로젝트 경로에 맞춰 조정) ----
15
  from src.config.optimization_config import (
 
50
 
51
  # 3) If specific product is not produced on specific date, set it to 0
52
  ACTIVE = {t: {p: 1 for p in PRODUCT_LIST} for t in DATE_SPAN}
53
+ # Example: ACTIVE[2]['C'] = 0 # Disable product C on day 2
54
 
55
 
56
  def build_lines():
 
90
  print(f"[HIERARCHY] Production order: {len(sorted_products)} products")
91
  for i, p in enumerate(sorted_products[:10]): # Show first 10
92
  level = KIT_LEVELS.get(p, "unknown")
93
+ level_name = KitLevel.get_name(level)
94
  deps = KIT_DEPENDENCIES.get(p, [])
95
  print(f" {i+1}. {p} (level {level}={level_name}, deps: {len(deps)})")
96
 
 
104
  Calculate timing weight based on hierarchy level.
105
  Lower levels (prepacks) should be produced earlier.
106
  """
107
+ level = KIT_LEVELS.get(product, KitLevel.MASTER) # Default to master level
108
+ return KitLevel.get_timing_weight(level)
 
 
109
 
110
  def solve_fixed_team_weekly():
111
  # --- Sets ---
 
147
  total_demand = sum(DEMAND_DICTIONARY.get(p, 0) for p in P)
148
 
149
  # Calculate maximum capacity with regular + overtime shifts only
150
+ regular_overtime_shifts = [s for s in S if s in ShiftType.REGULAR_AND_OVERTIME]
151
  max_capacity = 0
152
 
153
  for p in P:
 
267
 
268
  # 3) Product-line type compatibility + (optional) activity by day
269
  for p in P:
270
+ req_lt = KIT_LINE_MATCH_DICT.get(p, LineType.LONG_LINE) # Default to long line if not found
271
  req_total = sum(TEAM_REQ_PER_PRODUCT[e][p] for e in E)
272
  for ell in L:
273
  allowed = (ell[0] == req_lt) and (req_total <= max_workers_line.get(ell[0], 1e9))
 
316
  )
317
 
318
  # 7) Shift ordering constraints (only apply if shifts are available)
319
+ # Evening shift after regular shift
320
+ if ShiftType.EVENING in S and ShiftType.REGULAR in S: # Only if both shifts are available
321
  for e in E:
322
  for t in D:
323
  solver.Add(
324
+ solver.Sum(TEAM_REQ_PER_PRODUCT[e][p] * T[p, ell, ShiftType.EVENING, t] for p in P for ell in L)
325
  <=
326
+ solver.Sum(TEAM_REQ_PER_PRODUCT[e][p] * T[p, ell, ShiftType.REGULAR, t] for p in P for ell in L)
327
  )
328
 
329
+ # Overtime shift after regular shift
330
+ if ShiftType.OVERTIME in S and ShiftType.REGULAR in S: # Only if both shifts are available
331
  for e in E:
332
  for t in D:
333
  solver.Add(
334
+ solver.Sum(TEAM_REQ_PER_PRODUCT[e][p] * T[p, ell, ShiftType.OVERTIME, t] for p in P for ell in L)
335
  <=
336
+ solver.Sum(TEAM_REQ_PER_PRODUCT[e][p] * T[p, ell, ShiftType.REGULAR, t] for p in P for ell in L)
337
  )
338
 
339
  # 7.5) Bulk payment linking constraints are now handled inline in the cost calculation
 
370
  # --- Solve ---
371
  status = solver.Solve()
372
  if status != pywraplp.Solver.OPTIMAL:
373
+ status_names = {pywraplp.Solver.INFEASIBLE: "INFEASIBLE", pywraplp.Solver.UNBOUNDED: "UNBOUNDED"}
374
+ print(f"No optimal solution. Status: {status} ({status_names.get(status, 'UNKNOWN')})")
375
  # Debug hint:
376
  # solver.EnableOutput()
377
  # solver.ExportModelAsLpFile("model.lp")
 
433
 
434
  print("\n--- Schedule (line, shift, day) ---")
435
  for row in schedule:
436
+ shift_name = ShiftType.get_name(row['shift'])
437
+ line_name = LineType.get_name(row['line_type_id'])
438
+ print(f"D{row['day']} {line_name}-{row['line_idx']} {shift_name}: "
439
  f"{row['product']} T={row['run_hours']:.2f}h U={row['units']:.1f}")
440
 
441
  print("\n--- Implied headcount need (per type/shift/day) ---")
442
  for row in headcount:
443
+ shift_name = ShiftType.get_name(row['shift'])
444
+ print(f"{row['emp_type']}, {shift_name}, D{row['day']}: "
445
  f"need={row['needed']} (avail {row['available']})")
446
 
447
  print("\n--- Total person-hours by type/day ---")