HaLim
commited on
Commit
·
42b5ea5
1
Parent(s):
ef24926
Finalize optimizer with shift type / partial&bulk work type etc
Browse files
src/config/optimization_config.py
CHANGED
|
@@ -220,8 +220,16 @@ def get_cost_list_per_emp_shift():
|
|
| 220 |
print(f"using default value for cost list per emp shift")
|
| 221 |
shift_cost_df = extract.read_shift_cost_data()
|
| 222 |
#question - Important : there is multiple type of employment type in terms of the cost
|
| 223 |
-
#1
|
| 224 |
-
return {"UNICEF Fixed term":{1:43,2:43,3:64},"Humanizer":{1:27,2:27,3:41}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 225 |
|
| 226 |
COST_LIST_PER_EMP_SHIFT = get_cost_list_per_emp_shift()
|
| 227 |
# print("cost list per emp shift",COST_LIST_PER_EMP_SHIFT)
|
|
@@ -285,16 +293,29 @@ TEAM_REQ_PER_PRODUCT = get_team_requirements(PRODUCT_LIST)
|
|
| 285 |
print("team requirements per product:", TEAM_REQ_PER_PRODUCT)
|
| 286 |
|
| 287 |
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
# available employee but for fixed in shift 1, it is mandatory employment
|
| 295 |
|
| 296 |
MAX_HOUR_PER_PERSON_PER_DAY = 14 # legal standard
|
| 297 |
-
MAX_HOUR_PER_SHIFT_PER_PERSON = {1: 7, 2: 7, 3:
|
| 298 |
def get_per_product_speed():
|
| 299 |
try:
|
| 300 |
streamlit_per_product_speed = dashboard.per_product_speed
|
|
@@ -344,3 +365,34 @@ DAILY_WEEKLY_SCHEDULE = "daily" # daily or weekly ,this needs to be implemented
|
|
| 344 |
# "none" - Purely demand-driven scheduling (cost-efficient)
|
| 345 |
FIXED_STAFF_CONSTRAINT_MODE = "priority" # Recommended: "priority" for realistic business model
|
| 346 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
print(f"using default value for cost list per emp shift")
|
| 221 |
shift_cost_df = extract.read_shift_cost_data()
|
| 222 |
#question - Important : there is multiple type of employment type in terms of the cost
|
| 223 |
+
#Shift 1 = normal, 2 = evening, 3 = overtime
|
| 224 |
+
return {"UNICEF Fixed term":{1:43.27,2:43.27,3:64.91},"Humanizer":{1:27.94,2:27.94,3:41.91}}
|
| 225 |
+
|
| 226 |
+
def shift_code_to_name():
|
| 227 |
+
shift_code_to_name_dict = {
|
| 228 |
+
1: "normal",
|
| 229 |
+
2: "evening",
|
| 230 |
+
3: "overtime"
|
| 231 |
+
}
|
| 232 |
+
return shift_code_to_name_dict
|
| 233 |
|
| 234 |
COST_LIST_PER_EMP_SHIFT = get_cost_list_per_emp_shift()
|
| 235 |
# print("cost list per emp shift",COST_LIST_PER_EMP_SHIFT)
|
|
|
|
| 293 |
print("team requirements per product:", TEAM_REQ_PER_PRODUCT)
|
| 294 |
|
| 295 |
|
| 296 |
+
def get_max_employee_per_type_on_day():
|
| 297 |
+
try:
|
| 298 |
+
max_employee_per_type_on_day = dashboard.max_employee_per_type_on_day
|
| 299 |
+
return max_employee_per_type_on_day
|
| 300 |
+
except Exception as e:
|
| 301 |
+
print(f"using default value for max employee per type on day")
|
| 302 |
+
max_employee_per_type_on_day = {
|
| 303 |
+
"UNICEF Fixed term": {
|
| 304 |
+
t: 8 for t in DATE_SPAN
|
| 305 |
+
},
|
| 306 |
+
"Humanizer": {
|
| 307 |
+
t: 10 for t in DATE_SPAN
|
| 308 |
+
}
|
| 309 |
+
}
|
| 310 |
+
return max_employee_per_type_on_day
|
| 311 |
+
|
| 312 |
+
MAX_EMPLOYEE_PER_TYPE_ON_DAY = get_max_employee_per_type_on_day()
|
| 313 |
+
print("max employee per type on day",MAX_EMPLOYEE_PER_TYPE_ON_DAY)
|
| 314 |
+
|
| 315 |
# available employee but for fixed in shift 1, it is mandatory employment
|
| 316 |
|
| 317 |
MAX_HOUR_PER_PERSON_PER_DAY = 14 # legal standard
|
| 318 |
+
MAX_HOUR_PER_SHIFT_PER_PERSON = {1: 7.5, 2: 7.5, 3: 5} #1 = normal, 2 = evening, 3 = overtime
|
| 319 |
def get_per_product_speed():
|
| 320 |
try:
|
| 321 |
streamlit_per_product_speed = dashboard.per_product_speed
|
|
|
|
| 365 |
# "none" - Purely demand-driven scheduling (cost-efficient)
|
| 366 |
FIXED_STAFF_CONSTRAINT_MODE = "priority" # Recommended: "priority" for realistic business model
|
| 367 |
|
| 368 |
+
|
| 369 |
+
def get_payment_mode_config():
|
| 370 |
+
"""
|
| 371 |
+
Get payment mode configuration - try from streamlit session state first, then default values
|
| 372 |
+
Payment modes:
|
| 373 |
+
- "bulk": If employee works any hours in shift, pay for full shift hours
|
| 374 |
+
- "partial": Pay only for actual hours worked
|
| 375 |
+
"""
|
| 376 |
+
try:
|
| 377 |
+
# Try to get from streamlit session state (from Dataset Metadata page)
|
| 378 |
+
import streamlit as st
|
| 379 |
+
if hasattr(st, 'session_state') and 'payment_mode_config' in st.session_state:
|
| 380 |
+
print(f"Using payment mode config from streamlit session: {st.session_state.payment_mode_config}")
|
| 381 |
+
return st.session_state.payment_mode_config
|
| 382 |
+
except Exception as e:
|
| 383 |
+
print(f"Could not get payment mode config from streamlit session: {e}")
|
| 384 |
+
|
| 385 |
+
# Default payment mode configuration
|
| 386 |
+
# Shift 1: bulk, Shift 2: bulk (evening), Shift 3: partial (overtime)
|
| 387 |
+
print(f"Loading default payment mode configuration")
|
| 388 |
+
payment_mode_config = {
|
| 389 |
+
1: "bulk", # Regular shift - bulk payment
|
| 390 |
+
2: "bulk", # Evening shift - bulk payment
|
| 391 |
+
3: "partial" # Overtime shift - partial payment
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
return payment_mode_config
|
| 395 |
+
|
| 396 |
+
PAYMENT_MODE_CONFIG = get_payment_mode_config()
|
| 397 |
+
print("Payment mode configuration:", PAYMENT_MODE_CONFIG)
|
| 398 |
+
|
src/models/optimizer_new_aug14.py
CHANGED
|
@@ -28,6 +28,7 @@ from src.config.optimization_config import (
|
|
| 28 |
DAILY_WEEKLY_SCHEDULE, # 'daily' or 'weekly' (여기선 weekly로 모델링)
|
| 29 |
FIXED_STAFF_CONSTRAINT_MODE, # not used in fixed-team model (동시 투입이라 무의미)
|
| 30 |
TEAM_REQ_PER_PRODUCT, # {emp_type: {product: team_size}} from Kits_Calculation.csv
|
|
|
|
| 31 |
KIT_LINE_MATCH_DICT,
|
| 32 |
EVENING_SHIFT_MODE,
|
| 33 |
EVENING_SHIFT_DEMAND_THRESHOLD,
|
|
@@ -197,12 +198,47 @@ def solve_fixed_team_weekly():
|
|
| 197 |
T[p, ell, s, t] = solver.NumVar(0, Hmax_s[s], f"T_{p}_{ell[0]}_{ell[1]}_s{s}_d{t}")
|
| 198 |
U[p, ell, s, t] = solver.NumVar(0, INF, f"U_{p}_{ell[0]}_{ell[1]}_s{s}_d{t}")
|
| 199 |
|
| 200 |
-
#
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 206 |
|
| 207 |
# Secondary objective: encourage earlier production of dependencies (soft constraint)
|
| 208 |
# Small weight (0.01) to prioritize hierarchy without overwhelming cost optimization
|
|
@@ -290,6 +326,8 @@ def solve_fixed_team_weekly():
|
|
| 290 |
)
|
| 291 |
# (if needed, evening(3) after usual(1): sum(...)_s=3 ≤ sum(...)_s=1)
|
| 292 |
|
|
|
|
|
|
|
| 293 |
# 8) *** HIERARCHY DEPENDENCY CONSTRAINTS ***
|
| 294 |
# For subkits with prepack dependencies: dependencies should be produced before or same time
|
| 295 |
print("\n[HIERARCHY] Adding dependency constraints...")
|
|
|
|
| 28 |
DAILY_WEEKLY_SCHEDULE, # 'daily' or 'weekly' (여기선 weekly로 모델링)
|
| 29 |
FIXED_STAFF_CONSTRAINT_MODE, # not used in fixed-team model (동시 투입이라 무의미)
|
| 30 |
TEAM_REQ_PER_PRODUCT, # {emp_type: {product: team_size}} from Kits_Calculation.csv
|
| 31 |
+
PAYMENT_MODE_CONFIG, # {shift: 'bulk'/'partial'} payment mode configuration
|
| 32 |
KIT_LINE_MATCH_DICT,
|
| 33 |
EVENING_SHIFT_MODE,
|
| 34 |
EVENING_SHIFT_DEMAND_THRESHOLD,
|
|
|
|
| 198 |
T[p, ell, s, t] = solver.NumVar(0, Hmax_s[s], f"T_{p}_{ell[0]}_{ell[1]}_s{s}_d{t}")
|
| 199 |
U[p, ell, s, t] = solver.NumVar(0, INF, f"U_{p}_{ell[0]}_{ell[1]}_s{s}_d{t}")
|
| 200 |
|
| 201 |
+
# Note: Binary variables for bulk payment are now created inline in the cost calculation
|
| 202 |
+
|
| 203 |
+
# --- Objective: total labor cost with payment modes + hierarchy timing penalty ---
|
| 204 |
+
print(f"Payment mode configuration: {PAYMENT_MODE_CONFIG}")
|
| 205 |
+
|
| 206 |
+
# Build cost terms based on payment mode
|
| 207 |
+
cost_terms = []
|
| 208 |
+
|
| 209 |
+
for e in E:
|
| 210 |
+
for s in S:
|
| 211 |
+
payment_mode = PAYMENT_MODE_CONFIG.get(s, "partial") # Default to partial if not specified
|
| 212 |
+
|
| 213 |
+
if payment_mode == "partial":
|
| 214 |
+
# Partial payment: pay for actual hours worked
|
| 215 |
+
for p in P:
|
| 216 |
+
for ell in L:
|
| 217 |
+
for t in D:
|
| 218 |
+
cost_terms.append(cost[e][s] * TEAM_REQ_PER_PRODUCT[e][p] * T[p, ell, s, t])
|
| 219 |
+
|
| 220 |
+
elif payment_mode == "bulk":
|
| 221 |
+
# Bulk payment: if employees work ANY hours in a shift, pay them for FULL shift hours
|
| 222 |
+
# BUT only pay the employees who actually work, not all employees of that type
|
| 223 |
+
for p in P:
|
| 224 |
+
for ell in L:
|
| 225 |
+
for t in D:
|
| 226 |
+
# Calculate actual employees working: TEAM_REQ_PER_PRODUCT[e][p] employees work T[p,ell,s,t] hours
|
| 227 |
+
# For bulk payment: if T[p,ell,s,t] > 0, pay TEAM_REQ_PER_PRODUCT[e][p] employees for full shift
|
| 228 |
+
# We need a binary variable for each (e,s,p,ell,t) combination
|
| 229 |
+
# But we can use the existing logic: if T > 0, then those specific employees get bulk pay
|
| 230 |
+
|
| 231 |
+
# Create binary variable for this specific work assignment
|
| 232 |
+
work_binary = solver.BoolVar(f"work_{e}_s{s}_{p}_{ell[0]}{ell[1]}_d{t}")
|
| 233 |
+
|
| 234 |
+
# Link work_binary to T[p,ell,s,t]: work_binary = 1 if T > 0
|
| 235 |
+
solver.Add(T[p, ell, s, t] <= Hmax_s[s] * work_binary)
|
| 236 |
+
solver.Add(work_binary * 0.001 <= T[p, ell, s, t])
|
| 237 |
+
|
| 238 |
+
# Cost: pay the specific working employees for full shift hours
|
| 239 |
+
cost_terms.append(cost[e][s] * Hmax_s[s] * TEAM_REQ_PER_PRODUCT[e][p] * work_binary)
|
| 240 |
+
|
| 241 |
+
total_cost = solver.Sum(cost_terms)
|
| 242 |
|
| 243 |
# Secondary objective: encourage earlier production of dependencies (soft constraint)
|
| 244 |
# Small weight (0.01) to prioritize hierarchy without overwhelming cost optimization
|
|
|
|
| 326 |
)
|
| 327 |
# (if needed, evening(3) after usual(1): sum(...)_s=3 ≤ sum(...)_s=1)
|
| 328 |
|
| 329 |
+
# 7.5) Bulk payment linking constraints are now handled inline in the cost calculation
|
| 330 |
+
|
| 331 |
# 8) *** HIERARCHY DEPENDENCY CONSTRAINTS ***
|
| 332 |
# For subkits with prepack dependencies: dependencies should be produced before or same time
|
| 333 |
print("\n[HIERARCHY] Adding dependency constraints...")
|