haileyhalimj@gmail.com commited on
Commit
06404f5
·
1 Parent(s): 28016d1

Revert to 5ce2749d (roll back HEAD→5ce2...)

Browse files
src/config/optimization_config.py CHANGED
@@ -92,55 +92,59 @@ EVENING_SHIFT_MODE = "normal" # Default: only regular + overtime
92
  EVENING_SHIFT_DEMAND_THRESHOLD = 0.9 # Activate if regular+overtime capacity < 90% of demand
93
 
94
  def get_active_shift_list():
95
- """Get the list of active shifts based on EVENING_SHIFT_MODE or session state"""
96
- # Check session state first
97
- try:
98
- import streamlit as st
99
- if hasattr(st, 'session_state') and 'evening_shift_mode' in st.session_state:
100
- mode = st.session_state.evening_shift_mode
101
- else:
102
- mode = EVENING_SHIFT_MODE
103
- except:
104
- mode = EVENING_SHIFT_MODE
105
-
106
  all_shifts = get_shift_list()
107
 
108
- if mode == "normal":
109
- return [s for s in all_shifts if s in ShiftType.REGULAR_AND_OVERTIME]
110
- elif mode in ["activate_evening", "always_available"]:
111
- return list(all_shifts)
 
 
 
 
 
 
 
 
 
 
 
112
  else:
113
- return [s for s in all_shifts if s in ShiftType.REGULAR_AND_OVERTIME]
 
 
 
 
 
 
 
114
 
115
 
116
  def get_line_list():
117
- """Get line list from session state or default"""
118
  try:
 
119
  import streamlit as st
120
  if hasattr(st, 'session_state') and 'selected_lines' in st.session_state:
 
121
  return st.session_state.selected_lines
122
- except:
123
- pass
124
 
125
  # Default: load from data files
 
126
  line_df = extract.read_packaging_line_data()
127
- return line_df["id"].unique().tolist()
 
128
 
129
  # DO NOT load at import time - always call get_line_list() dynamically
130
  # LINE_LIST = get_line_list() # REMOVED - was causing stale data!
131
 
132
 
133
- # Module-level cache for kit line match - prevents reloading on every access
134
- _KIT_LINE_MATCH_DICT_CACHE = None
135
-
136
  def get_kit_line_match():
137
- """Get kit line match dictionary with caching to prevent reload on every import"""
138
- global _KIT_LINE_MATCH_DICT_CACHE
139
-
140
- # Return cached value if available
141
- if _KIT_LINE_MATCH_DICT_CACHE is not None:
142
- return _KIT_LINE_MATCH_DICT_CACHE
143
-
144
  kit_line_match = extract.read_kit_line_match_data()
145
  kit_line_match_dict = kit_line_match.set_index("kit_name")["line_type"].to_dict()
146
 
@@ -162,6 +166,7 @@ def get_kit_line_match():
162
  if line_id is not None:
163
  converted_dict[kit] = line_id
164
  else:
 
165
  # Default to long line if unknown
166
  converted_dict[kit] = LineType.LONG_LINE
167
  elif isinstance(line_name, (int, float)) and not pd.isna(line_name):
@@ -170,29 +175,26 @@ def get_kit_line_match():
170
  else:
171
  # Missing or empty line type - skip (no production needed for non-standalone masters)
172
  pass # Don't add to converted_dict - these kits won't have line assignments
173
-
174
- # Cache the result
175
- _KIT_LINE_MATCH_DICT_CACHE = converted_dict
176
  return converted_dict
177
 
178
- # Lazy property - will be initialized on first access
179
- @property
180
- def KIT_LINE_MATCH_DICT():
181
- return get_kit_line_match()
182
 
183
 
184
  def get_line_cnt_per_type():
185
- """Get line counts from session state or default"""
186
  try:
 
187
  import streamlit as st
188
  if hasattr(st, 'session_state') and 'line_counts' in st.session_state:
 
189
  return st.session_state.line_counts
190
- except:
191
- pass
192
 
193
- # Default: load from data files
194
  line_df = extract.read_packaging_line_data()
195
  line_cnt_per_type = line_df.set_index("id")["line_count"].to_dict()
 
196
  return line_cnt_per_type
197
 
198
  # DO NOT load at import time - always call get_line_cnt_per_type() dynamically
@@ -212,23 +214,28 @@ def get_demand_dictionary(force_reload=False):
212
  filter_instance.load_data(force_reload=True)
213
 
214
  demand_dictionary = filter_instance.get_filtered_demand_dictionary()
 
 
215
  return demand_dictionary
216
  except Exception as e:
 
217
  raise Exception("Demand dictionary not found with error:"+str(e))
218
 
219
  # DO NOT load at import time - always call get_demand_dictionary() dynamically
220
  # DEMAND_DICTIONARY = get_demand_dictionary() # REMOVED - was causing stale data!
221
 
222
  def get_cost_list_per_emp_shift():
223
- """Get cost list from session state or default"""
224
  try:
 
225
  import streamlit as st
226
  if hasattr(st, 'session_state') and 'cost_list_per_emp_shift' in st.session_state:
 
227
  return st.session_state.cost_list_per_emp_shift
228
- except:
229
- pass
230
 
231
- # Default hourly rates
 
232
  return DefaultConfig.DEFAULT_COST_RATES
233
 
234
  def shift_code_to_name():
@@ -240,6 +247,18 @@ def line_code_to_name():
240
 
241
  # DO NOT load at import time - always call get_cost_list_per_emp_shift() dynamically
242
  # COST_LIST_PER_EMP_SHIFT = get_cost_list_per_emp_shift() # REMOVED - was causing stale data!
 
 
 
 
 
 
 
 
 
 
 
 
243
 
244
 
245
  def get_team_requirements(product_list=None):
@@ -250,9 +269,20 @@ def get_team_requirements(product_list=None):
250
  if product_list is None:
251
  product_list = get_product_list() # Get fresh product list
252
 
 
 
 
 
 
 
 
 
253
  # Read the kits calculation data directly
254
  kits_df = extract.read_personnel_requirement_data()
255
-
 
 
 
256
  # Initialize the team requirements dictionary
257
  team_req_dict = {
258
  "UNICEF Fixed term": {},
@@ -261,7 +291,10 @@ def get_team_requirements(product_list=None):
261
 
262
  # Process each product in the product list
263
  for product in product_list:
 
 
264
  product_data = kits_df[kits_df['Kit'] == product]
 
265
  if not product_data.empty:
266
  # Extract Humanizer and UNICEF staff requirements
267
  humanizer_req = product_data["Humanizer"].iloc[0]
@@ -270,6 +303,9 @@ def get_team_requirements(product_list=None):
270
  # Convert to int (data is already cleaned in extract function)
271
  team_req_dict["Humanizer"][product] = int(humanizer_req)
272
  team_req_dict["UNICEF Fixed term"][product] = int(unicef_req)
 
 
 
273
 
274
  return team_req_dict
275
 
@@ -278,22 +314,22 @@ def get_team_requirements(product_list=None):
278
 
279
 
280
  def get_max_employee_per_type_on_day():
281
- """Get max employee counts from session state or default"""
282
  try:
 
283
  import streamlit as st
284
  if hasattr(st, 'session_state') and 'max_employee_per_type_on_day' in st.session_state:
 
285
  return st.session_state.max_employee_per_type_on_day
286
- except:
287
- pass
288
 
289
- # Default: get date span dynamically
290
- date_span, _, _ = get_date_span()
291
  max_employee_per_type_on_day = {
292
  "UNICEF Fixed term": {
293
- t: 8 for t in date_span
294
  },
295
  "Humanizer": {
296
- t: 10 for t in date_span
297
  }
298
  }
299
  return max_employee_per_type_on_day
@@ -310,8 +346,8 @@ def get_max_hour_per_shift_per_person():
310
  import streamlit as st
311
  if hasattr(st, 'session_state') and 'max_hour_per_shift_per_person' in st.session_state:
312
  return st.session_state.max_hour_per_shift_per_person
313
- except:
314
- pass
315
 
316
  # Fallback to default only if not configured by user
317
  return DefaultConfig.MAX_HOUR_PER_SHIFT_PER_PERSON
@@ -319,6 +355,13 @@ def get_max_hour_per_shift_per_person():
319
  # DO NOT load at import time - always call get_max_hour_per_shift_per_person() dynamically
320
  # MAX_HOUR_PER_SHIFT_PER_PERSON = get_max_hour_per_shift_per_person() # REMOVED - was causing stale data!
321
 
 
 
 
 
 
 
 
322
  # Keep these complex getters that access DefaultConfig or have complex logic:
323
  def get_evening_shift_demand_threshold():
324
  """Get evening shift demand threshold - checks Streamlit session state first"""
@@ -326,8 +369,8 @@ def get_evening_shift_demand_threshold():
326
  import streamlit as st
327
  if hasattr(st, 'session_state') and 'evening_shift_demand_threshold' in st.session_state:
328
  return st.session_state.evening_shift_demand_threshold
329
- except:
330
- pass
331
 
332
  # Fallback to default only if not configured by user
333
  return getattr(DefaultConfig, 'EVENING_SHIFT_DEMAND_THRESHOLD', 10000)
@@ -338,8 +381,8 @@ def get_fixed_min_unicef_per_day():
338
  import streamlit as st
339
  if hasattr(st, 'session_state') and 'fixed_min_unicef_per_day' in st.session_state:
340
  return st.session_state.fixed_min_unicef_per_day
341
- except:
342
- pass
343
 
344
  # Fallback to default only if not configured by user
345
  return getattr(DefaultConfig, 'FIXED_MIN_UNICEF_PER_DAY', {1: 1, 2: 1, 3: 1, 4: 1, 5: 1})
@@ -377,45 +420,22 @@ def _ensure_fresh_config():
377
  # after all functions are defined. This ensures all getter functions are available.
378
 
379
  # ---- Kit Hierarchy for Production Ordering ----
380
- # Module-level cache for hierarchy - prevents reloading on every import
381
- _KIT_HIERARCHY_CACHE = None
382
-
383
  def get_kit_hierarchy_data():
384
- """Load kit hierarchy data from extract functions with caching"""
385
- global _KIT_HIERARCHY_CACHE
386
-
387
- # Return cached value if available
388
- if _KIT_HIERARCHY_CACHE is not None:
389
- return _KIT_HIERARCHY_CACHE
390
-
 
391
  # Get hierarchy data from extract functions
392
  kit_levels, dependencies, priority_order = extract.get_production_order_data()
393
 
394
- # Cache the result
395
- _KIT_HIERARCHY_CACHE = (kit_levels, dependencies, priority_order)
396
  return kit_levels, dependencies, priority_order
397
 
398
- # Lazy properties - will be initialized on first access
399
- def get_kit_levels():
400
- """Get kit levels lazily"""
401
- kit_levels, _, _ = get_kit_hierarchy_data()
402
- return kit_levels
403
-
404
- def get_kit_dependencies():
405
- """Get kit dependencies lazily"""
406
- _, dependencies, _ = get_kit_hierarchy_data()
407
- return dependencies
408
-
409
- def get_production_priority_order():
410
- """Get production priority order lazily"""
411
- _, _, priority_order = get_kit_hierarchy_data()
412
- return priority_order
413
-
414
- # Initialize only on first access - use properties
415
- KIT_LEVELS = None
416
- KIT_DEPENDENCIES = None
417
- PRODUCTION_PRIORITY_ORDER = None
418
- KIT_LINE_MATCH_DICT = None
419
 
420
  def get_max_parallel_workers():
421
  """Get max parallel workers - checks Streamlit session state first"""
@@ -423,8 +443,8 @@ def get_max_parallel_workers():
423
  import streamlit as st
424
  if hasattr(st, 'session_state') and 'max_parallel_workers' in st.session_state:
425
  return st.session_state.max_parallel_workers
426
- except:
427
- pass
428
 
429
  # Fallback to default only if not configured by user
430
  return DefaultConfig.MAX_PARALLEL_WORKERS
@@ -450,9 +470,10 @@ def get_fixed_min_unicef_per_day():
450
  try:
451
  import streamlit as st
452
  if hasattr(st, 'session_state') and 'fixed_min_unicef_per_day' in st.session_state:
 
453
  return st.session_state.fixed_min_unicef_per_day
454
- except:
455
- pass
456
 
457
  # Default value - minimum UNICEF Fixed term employees required per day
458
  return 2
@@ -470,14 +491,19 @@ def get_payment_mode_config():
470
  - "partial": Pay only for actual hours worked
471
  """
472
  try:
 
473
  import streamlit as st
474
  if hasattr(st, 'session_state') and 'payment_mode_config' in st.session_state:
 
475
  return st.session_state.payment_mode_config
476
- except:
477
- pass
478
 
479
  # Default payment mode configuration
480
- return DefaultConfig.PAYMENT_MODE_CONFIG
 
 
 
481
 
482
  # DO NOT load at import time - always call get_payment_mode_config() dynamically
483
 
@@ -491,8 +517,24 @@ def get_payment_mode_config():
491
  # This prevents the infinite loop where importing this module triggers Streamlit session access
492
  # which causes the app to reload, which imports this module again, etc.
493
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
494
  # Note: These variables are initialized once at import time with default/fallback values.
495
  # To get fresh values after changing Streamlit configuration, either:
496
  # 1. Call the get_*() functions directly (RECOMMENDED for dynamic use)
497
  # 2. Call _ensure_fresh_config() to refresh all module-level variables
498
  # 3. Use importlib.reload() to reload the entire module
 
 
92
  EVENING_SHIFT_DEMAND_THRESHOLD = 0.9 # Activate if regular+overtime capacity < 90% of demand
93
 
94
  def get_active_shift_list():
95
+ """
96
+ Get the list of active shifts based on EVENING_SHIFT_MODE setting.
97
+ """
 
 
 
 
 
 
 
 
98
  all_shifts = get_shift_list()
99
 
100
+ if EVENING_SHIFT_MODE == "normal":
101
+ # Only regular and overtime shifts - NO evening shift
102
+ active_shifts = [s for s in all_shifts if s in ShiftType.REGULAR_AND_OVERTIME]
103
+ print(f"[SHIFT MODE] Normal mode: Using shifts {active_shifts} (Regular + Overtime only, NO evening)")
104
+
105
+ elif EVENING_SHIFT_MODE == "activate_evening":
106
+ # All shifts including evening (2)
107
+ active_shifts = list(all_shifts)
108
+ print(f"[SHIFT MODE] Evening activated: Using all shifts {active_shifts}")
109
+
110
+ elif EVENING_SHIFT_MODE == "always_available":
111
+ # All shifts always available
112
+ active_shifts = list(all_shifts)
113
+ print(f"[SHIFT MODE] Always available: Using all shifts {active_shifts}")
114
+
115
  else:
116
+ # Default to normal mode
117
+ active_shifts = [s for s in all_shifts if s in ShiftType.REGULAR_AND_OVERTIME]
118
+ print(f"[SHIFT MODE] Unknown mode '{EVENING_SHIFT_MODE}', defaulting to normal: {active_shifts}")
119
+
120
+ return active_shifts
121
+
122
+ # DO NOT load at import time - always call get_active_shift_list() dynamically
123
+ # SHIFT_LIST = get_active_shift_list() # REMOVED - was causing stale data!
124
 
125
 
126
  def get_line_list():
127
+ """Get line list - try from streamlit session state first, then from data files"""
128
  try:
129
+ # Try to get from streamlit session state (from Dataset Metadata page)
130
  import streamlit as st
131
  if hasattr(st, 'session_state') and 'selected_lines' in st.session_state:
132
+ print(f"Using lines from Dataset Metadata page: {st.session_state.selected_lines}")
133
  return st.session_state.selected_lines
134
+ except Exception as e:
135
+ print(f"Could not get lines from streamlit session: {e}")
136
 
137
  # Default: load from data files
138
+ print(f"Loading line list from data files")
139
  line_df = extract.read_packaging_line_data()
140
+ line_list = line_df["id"].unique().tolist()
141
+ return line_list
142
 
143
  # DO NOT load at import time - always call get_line_list() dynamically
144
  # LINE_LIST = get_line_list() # REMOVED - was causing stale data!
145
 
146
 
 
 
 
147
  def get_kit_line_match():
 
 
 
 
 
 
 
148
  kit_line_match = extract.read_kit_line_match_data()
149
  kit_line_match_dict = kit_line_match.set_index("kit_name")["line_type"].to_dict()
150
 
 
166
  if line_id is not None:
167
  converted_dict[kit] = line_id
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):
 
175
  else:
176
  # Missing or empty line type - skip (no production needed for non-standalone masters)
177
  pass # Don't add to converted_dict - these kits won't have line assignments
178
+
 
 
179
  return converted_dict
180
 
181
+ KIT_LINE_MATCH_DICT = get_kit_line_match()
 
 
 
182
 
183
 
184
  def get_line_cnt_per_type():
 
185
  try:
186
+ # Try to get from streamlit session state (from config page)
187
  import streamlit as st
188
  if hasattr(st, 'session_state') and 'line_counts' in st.session_state:
189
+ print(f"Using line counts from config page: {st.session_state.line_counts}")
190
  return st.session_state.line_counts
191
+ except Exception as e:
192
+ print(f"Could not get line counts from streamlit session: {e}")
193
 
194
+ print(f"Loading default line count values from data files")
195
  line_df = extract.read_packaging_line_data()
196
  line_cnt_per_type = line_df.set_index("id")["line_count"].to_dict()
197
+ print("line cnt per type", line_cnt_per_type)
198
  return line_cnt_per_type
199
 
200
  # DO NOT load at import time - always call get_line_cnt_per_type() dynamically
 
214
  filter_instance.load_data(force_reload=True)
215
 
216
  demand_dictionary = filter_instance.get_filtered_demand_dictionary()
217
+ print(f"📈 FRESH FILTERED DEMAND: {len(demand_dictionary)} products with total demand {sum(demand_dictionary.values())}")
218
+ print(f"🔄 LOADED DYNAMICALLY: Reflects current Streamlit configs")
219
  return demand_dictionary
220
  except Exception as e:
221
+ print(f"Error loading dynamic demand dictionary: {e}")
222
  raise Exception("Demand dictionary not found with error:"+str(e))
223
 
224
  # DO NOT load at import time - always call get_demand_dictionary() dynamically
225
  # DEMAND_DICTIONARY = get_demand_dictionary() # REMOVED - was causing stale data!
226
 
227
  def get_cost_list_per_emp_shift():
 
228
  try:
229
+ # Try to get from streamlit session state (from config page)
230
  import streamlit as st
231
  if hasattr(st, 'session_state') and 'cost_list_per_emp_shift' in st.session_state:
232
+ print(f"Using cost list from config page: {st.session_state.cost_list_per_emp_shift}")
233
  return st.session_state.cost_list_per_emp_shift
234
+ except Exception as e:
235
+ print(f"Could not get cost list from streamlit session: {e}")
236
 
237
+ print(f"Loading default cost values")
238
+ # Default hourly rates - Important: multiple employment types with different costs
239
  return DefaultConfig.DEFAULT_COST_RATES
240
 
241
  def shift_code_to_name():
 
247
 
248
  # DO NOT load at import time - always call get_cost_list_per_emp_shift() dynamically
249
  # COST_LIST_PER_EMP_SHIFT = get_cost_list_per_emp_shift() # REMOVED - was causing stale data!
250
+
251
+
252
+
253
+ # COST_LIST_PER_EMP_SHIFT = { # WH_Workforce_Hourly_Pay_Scale
254
+ # "Fixed": {1: 0, 2: 22, 3: 18},
255
+ # "Humanizer": {1: 10, 2: 10, 3: 10},
256
+ # }
257
+
258
+
259
+
260
+
261
+
262
 
263
 
264
  def get_team_requirements(product_list=None):
 
269
  if product_list is None:
270
  product_list = get_product_list() # Get fresh product list
271
 
272
+ try:
273
+ # Check if streamlit has this data (for future extension)
274
+ # streamlit_team_req = dashboard.team_requirements
275
+ # return streamlit_team_req
276
+ pass
277
+ except Exception as e:
278
+ print(f"Using default value for team requirements, extracting from CSV: {e}")
279
+
280
  # Read the kits calculation data directly
281
  kits_df = extract.read_personnel_requirement_data()
282
+ # kits_path = "data/real_data_excel/converted_csv/Kits__Calculation.csv"
283
+ # kits_df = pd.read_csv(kits_path)
284
+ print("kits_df columns:", kits_df.columns.tolist())
285
+ print("kits_df head:", kits_df.head())
286
  # Initialize the team requirements dictionary
287
  team_req_dict = {
288
  "UNICEF Fixed term": {},
 
291
 
292
  # Process each product in the product list
293
  for product in product_list:
294
+ print("product",product)
295
+ print(f"Processing team requirements for product: {product}")
296
  product_data = kits_df[kits_df['Kit'] == product]
297
+ print("product_data",product_data)
298
  if not product_data.empty:
299
  # Extract Humanizer and UNICEF staff requirements
300
  humanizer_req = product_data["Humanizer"].iloc[0]
 
303
  # Convert to int (data is already cleaned in extract function)
304
  team_req_dict["Humanizer"][product] = int(humanizer_req)
305
  team_req_dict["UNICEF Fixed term"][product] = int(unicef_req)
306
+ else:
307
+ print(f"Warning: Product {product} not found in Kits Calculation data, setting requirements to 0")
308
+
309
 
310
  return team_req_dict
311
 
 
314
 
315
 
316
  def get_max_employee_per_type_on_day():
 
317
  try:
318
+ # Try to get from streamlit session state (from config page)
319
  import streamlit as st
320
  if hasattr(st, 'session_state') and 'max_employee_per_type_on_day' in st.session_state:
321
+ print(f"Using max employee counts from config page: {st.session_state.max_employee_per_type_on_day}")
322
  return st.session_state.max_employee_per_type_on_day
323
+ except Exception as e:
324
+ print(f"Could not get max employee counts from streamlit session: {e}")
325
 
326
+ print(f"Loading default max employee values")
 
327
  max_employee_per_type_on_day = {
328
  "UNICEF Fixed term": {
329
+ t: 8 for t in DATE_SPAN
330
  },
331
  "Humanizer": {
332
+ t: 10 for t in DATE_SPAN
333
  }
334
  }
335
  return max_employee_per_type_on_day
 
346
  import streamlit as st
347
  if hasattr(st, 'session_state') and 'max_hour_per_shift_per_person' in st.session_state:
348
  return st.session_state.max_hour_per_shift_per_person
349
+ except Exception as e:
350
+ print(f"Could not get max hours per shift from session: {e}")
351
 
352
  # Fallback to default only if not configured by user
353
  return DefaultConfig.MAX_HOUR_PER_SHIFT_PER_PERSON
 
355
  # DO NOT load at import time - always call get_max_hour_per_shift_per_person() dynamically
356
  # MAX_HOUR_PER_SHIFT_PER_PERSON = get_max_hour_per_shift_per_person() # REMOVED - was causing stale data!
357
 
358
+ # Removed unnecessary getter functions - use direct imports instead:
359
+ # - MAX_HOUR_PER_PERSON_PER_DAY
360
+ # - MAX_HOUR_PER_SHIFT_PER_PERSON
361
+ # - KIT_LINE_MATCH_DICT
362
+ # - MAX_PARALLEL_WORKERS
363
+ # - EVENING_SHIFT_MODE
364
+
365
  # Keep these complex getters that access DefaultConfig or have complex logic:
366
  def get_evening_shift_demand_threshold():
367
  """Get evening shift demand threshold - checks Streamlit session state first"""
 
369
  import streamlit as st
370
  if hasattr(st, 'session_state') and 'evening_shift_demand_threshold' in st.session_state:
371
  return st.session_state.evening_shift_demand_threshold
372
+ except Exception as e:
373
+ print(f"Could not get evening shift threshold from session: {e}")
374
 
375
  # Fallback to default only if not configured by user
376
  return getattr(DefaultConfig, 'EVENING_SHIFT_DEMAND_THRESHOLD', 10000)
 
381
  import streamlit as st
382
  if hasattr(st, 'session_state') and 'fixed_min_unicef_per_day' in st.session_state:
383
  return st.session_state.fixed_min_unicef_per_day
384
+ except Exception as e:
385
+ print(f"Could not get fixed min UNICEF from session: {e}")
386
 
387
  # Fallback to default only if not configured by user
388
  return getattr(DefaultConfig, 'FIXED_MIN_UNICEF_PER_DAY', {1: 1, 2: 1, 3: 1, 4: 1, 5: 1})
 
420
  # after all functions are defined. This ensures all getter functions are available.
421
 
422
  # ---- Kit Hierarchy for Production Ordering ----
 
 
 
423
  def get_kit_hierarchy_data():
424
+ try:
425
+ # Try to get from streamlit first (future extension)
426
+ # streamlit_hierarchy = dashboard.kit_hierarchy_data
427
+ # return streamlit_hierarchy
428
+ pass
429
+ except Exception as e:
430
+ print(f"Using default hierarchy data from extract: {e}")
431
+
432
  # Get hierarchy data from extract functions
433
  kit_levels, dependencies, priority_order = extract.get_production_order_data()
434
 
 
 
435
  return kit_levels, dependencies, priority_order
436
 
437
+ KIT_LEVELS, KIT_DEPENDENCIES, PRODUCTION_PRIORITY_ORDER = get_kit_hierarchy_data()
438
+ print(f"Kit Hierarchy loaded: {len(KIT_LEVELS)} kits, Priority order: {len(PRODUCTION_PRIORITY_ORDER)} items")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
439
 
440
  def get_max_parallel_workers():
441
  """Get max parallel workers - checks Streamlit session state first"""
 
443
  import streamlit as st
444
  if hasattr(st, 'session_state') and 'max_parallel_workers' in st.session_state:
445
  return st.session_state.max_parallel_workers
446
+ except Exception as e:
447
+ print(f"Could not get max parallel workers from session: {e}")
448
 
449
  # Fallback to default only if not configured by user
450
  return DefaultConfig.MAX_PARALLEL_WORKERS
 
470
  try:
471
  import streamlit as st
472
  if hasattr(st, 'session_state') and 'fixed_min_unicef_per_day' in st.session_state:
473
+ print(f"Using fixed minimum UNICEF per day from config page: {st.session_state.fixed_min_unicef_per_day}")
474
  return st.session_state.fixed_min_unicef_per_day
475
+ except ImportError:
476
+ pass # Streamlit not available in CLI mode
477
 
478
  # Default value - minimum UNICEF Fixed term employees required per day
479
  return 2
 
491
  - "partial": Pay only for actual hours worked
492
  """
493
  try:
494
+ # Try to get from streamlit session state (from Dataset Metadata page)
495
  import streamlit as st
496
  if hasattr(st, 'session_state') and 'payment_mode_config' in st.session_state:
497
+ print(f"Using payment mode config from streamlit session: {st.session_state.payment_mode_config}")
498
  return st.session_state.payment_mode_config
499
+ except Exception as e:
500
+ print(f"Could not get payment mode config from streamlit session: {e}")
501
 
502
  # Default payment mode configuration
503
+ print(f"Loading default payment mode configuration")
504
+ payment_mode_config = DefaultConfig.PAYMENT_MODE_CONFIG
505
+
506
+ return payment_mode_config
507
 
508
  # DO NOT load at import time - always call get_payment_mode_config() dynamically
509
 
 
517
  # This prevents the infinite loop where importing this module triggers Streamlit session access
518
  # which causes the app to reload, which imports this module again, etc.
519
 
520
+ # Initialize with default values (will use fallback data when no Streamlit session)
521
+ # PER_PRODUCT_SPEED = extract.read_package_speed_data()
522
+ # LINE_LIST = get_line_list()
523
+ # EMPLOYEE_TYPE_LIST = get_employee_type_list()
524
+ # SHIFT_LIST = get_active_shift_list()
525
+ # LINE_CNT_PER_TYPE = get_line_cnt_per_type()
526
+ # COST_LIST_PER_EMP_SHIFT = get_cost_list_per_emp_shift()
527
+ # MAX_EMPLOYEE_PER_TYPE_ON_DAY = get_max_employee_per_type_on_day()
528
+ # MAX_HOUR_PER_SHIFT_PER_PERSON = get_max_hour_per_shift_per_person()
529
+ # MAX_PARALLEL_WORKERS = get_max_parallel_workers()
530
+ # FIXED_MIN_UNICEF_PER_DAY = get_fixed_min_unicef_per_day()
531
+ # PAYMENT_MODE_CONFIG = get_payment_mode_config()
532
+
533
+ print("✅ Module-level configuration functions defined (variables initialized dynamically)")
534
+
535
  # Note: These variables are initialized once at import time with default/fallback values.
536
  # To get fresh values after changing Streamlit configuration, either:
537
  # 1. Call the get_*() functions directly (RECOMMENDED for dynamic use)
538
  # 2. Call _ensure_fresh_config() to refresh all module-level variables
539
  # 3. Use importlib.reload() to reload the entire module
540
+
src/visualization/__init__.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ """
2
+ Visualization package for Supply Roster Optimization Tool
3
+ Provides visualization dashboards and charts for optimization results
4
+ """
5
+
src/visualization/hierarchy_dashboard.py CHANGED
@@ -17,13 +17,12 @@ except ImportError:
17
 
18
  import numpy as np
19
  import sys
20
- sys.path.append('src')
21
 
22
- from config.optimization_config import (
23
  KIT_LEVELS, KIT_DEPENDENCIES, TEAM_REQ_PER_PRODUCT,
24
  shift_code_to_name, line_code_to_name
25
  )
26
- from config.constants import ShiftType, LineType, KitLevel
27
 
28
  # Import kit relationships dashboard
29
  try:
 
17
 
18
  import numpy as np
19
  import sys
 
20
 
21
+ from src.config.optimization_config import (
22
  KIT_LEVELS, KIT_DEPENDENCIES, TEAM_REQ_PER_PRODUCT,
23
  shift_code_to_name, line_code_to_name
24
  )
25
+ from src.config.constants import ShiftType, LineType, KitLevel
26
 
27
  # Import kit relationships dashboard
28
  try:
src/visualization/kit_relationships.py CHANGED
@@ -11,9 +11,8 @@ import plotly.graph_objects as go
11
  from plotly.subplots import make_subplots
12
  import json
13
  import sys
14
- sys.path.append('src')
15
 
16
- from config.constants import ShiftType, LineType, KitLevel
17
 
18
  # Optional networkx for advanced network layouts
19
  try:
 
11
  from plotly.subplots import make_subplots
12
  import json
13
  import sys
 
14
 
15
+ from src.config.constants import ShiftType, LineType, KitLevel
16
 
17
  # Optional networkx for advanced network layouts
18
  try:
ui/pages/config_page.py CHANGED
@@ -10,9 +10,6 @@ import os
10
  from src.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'))
15
-
16
  def render_config_page():
17
  """Render the configuration page with all user input controls"""
18
 
@@ -663,7 +660,6 @@ def save_configuration():
663
 
664
  # Get available demand dates from data to determine the actual date range
665
  try:
666
- sys.path.append('src')
667
  import src.preprocess.extract as extract
668
  import pandas as pd
669
  from datetime import datetime
@@ -918,7 +914,6 @@ def display_user_friendly_summary(config):
918
  # Demand Data Preview
919
  st.subheader("📊 Demand Data Preview")
920
  try:
921
- sys.path.append('src')
922
  import src.preprocess.extract as extract
923
  from datetime import datetime
924
 
@@ -1069,8 +1064,7 @@ def run_optimization():
1069
  st.info(f"👥 Max UNICEF/day: {st.session_state.max_unicef_per_day}, Max Humanizer/day: {st.session_state.max_humanizer_per_day}")
1070
 
1071
  # Import and run the optimization (after clearing)
1072
- sys.path.append('src')
1073
- from models.optimizer_real import run_optimization_for_week
1074
 
1075
  # Run the optimization
1076
  with st.spinner('Optimizing workforce schedule...'):
@@ -1101,12 +1095,6 @@ def check_critical_data_issues():
1101
 
1102
  try:
1103
  # Add src to path if needed
1104
- import sys
1105
- import os
1106
- src_path = os.path.join(os.path.dirname(__file__), 'src')
1107
- if src_path not in sys.path:
1108
- sys.path.append(src_path)
1109
-
1110
  from src.demand_validation_viz import DemandValidationViz
1111
 
1112
  # Initialize validator and load data
 
10
  from src.config import optimization_config
11
  from src.config.constants import ShiftType, LineType
12
 
 
 
 
13
  def render_config_page():
14
  """Render the configuration page with all user input controls"""
15
 
 
660
 
661
  # Get available demand dates from data to determine the actual date range
662
  try:
 
663
  import src.preprocess.extract as extract
664
  import pandas as pd
665
  from datetime import datetime
 
914
  # Demand Data Preview
915
  st.subheader("📊 Demand Data Preview")
916
  try:
 
917
  import src.preprocess.extract as extract
918
  from datetime import datetime
919
 
 
1064
  st.info(f"👥 Max UNICEF/day: {st.session_state.max_unicef_per_day}, Max Humanizer/day: {st.session_state.max_humanizer_per_day}")
1065
 
1066
  # Import and run the optimization (after clearing)
1067
+ from src.models.optimizer_real import run_optimization_for_week
 
1068
 
1069
  # Run the optimization
1070
  with st.spinner('Optimizing workforce schedule...'):
 
1095
 
1096
  try:
1097
  # Add src to path if needed
 
 
 
 
 
 
1098
  from src.demand_validation_viz import DemandValidationViz
1099
 
1100
  # Initialize validator and load data
ui/pages/optimization_results.py CHANGED
@@ -93,8 +93,7 @@ def display_weekly_summary(results):
93
 
94
  with col3:
95
  # Calculate fulfillment rate
96
- sys.path.append('src')
97
- from config.optimization_config import get_demand_dictionary
98
  DEMAND_DICTIONARY = get_demand_dictionary()
99
  total_demand = sum(DEMAND_DICTIONARY.values())
100
  fulfillment_rate = (total_production / total_demand * 100) if total_demand > 0 else 0
@@ -264,8 +263,7 @@ def display_line_schedules(results):
264
 
265
  # Process schedule data
266
  schedule_data = []
267
- sys.path.append('src')
268
- from config.optimization_config import get_team_requirements, get_demand_dictionary, shift_code_to_name, line_code_to_name
269
  TEAM_REQ_PER_PRODUCT = get_team_requirements()
270
  DEMAND_DICTIONARY = get_demand_dictionary()
271
 
@@ -504,8 +502,7 @@ def display_kit_production(results):
504
 
505
  # Weekly production summary
506
  production_data = []
507
- sys.path.append('src')
508
- from config.optimization_config import get_demand_dictionary
509
  DEMAND_DICTIONARY = get_demand_dictionary()
510
 
511
  for product, production in results['weekly_production'].items():
@@ -540,8 +537,7 @@ def display_cost_analysis(results):
540
  st.subheader("💰 Cost Breakdown Analysis")
541
 
542
  # Calculate cost breakdown
543
- sys.path.append('src')
544
- from config.optimization_config import get_cost_list_per_emp_shift, get_team_requirements, shift_code_to_name, line_code_to_name
545
  COST_LIST_PER_EMP_SHIFT = get_cost_list_per_emp_shift() # Dynamic call
546
  TEAM_REQ_PER_PRODUCT = get_team_requirements()
547
 
 
93
 
94
  with col3:
95
  # Calculate fulfillment rate
96
+ from src.config.optimization_config import get_demand_dictionary
 
97
  DEMAND_DICTIONARY = get_demand_dictionary()
98
  total_demand = sum(DEMAND_DICTIONARY.values())
99
  fulfillment_rate = (total_production / total_demand * 100) if total_demand > 0 else 0
 
263
 
264
  # Process schedule data
265
  schedule_data = []
266
+ from src.config.optimization_config import get_team_requirements, get_demand_dictionary, shift_code_to_name, line_code_to_name
 
267
  TEAM_REQ_PER_PRODUCT = get_team_requirements()
268
  DEMAND_DICTIONARY = get_demand_dictionary()
269
 
 
502
 
503
  # Weekly production summary
504
  production_data = []
505
+ from src.config.optimization_config import get_demand_dictionary
 
506
  DEMAND_DICTIONARY = get_demand_dictionary()
507
 
508
  for product, production in results['weekly_production'].items():
 
537
  st.subheader("💰 Cost Breakdown Analysis")
538
 
539
  # Calculate cost breakdown
540
+ from src.config.optimization_config import get_cost_list_per_emp_shift, get_team_requirements, shift_code_to_name, line_code_to_name
 
541
  COST_LIST_PER_EMP_SHIFT = get_cost_list_per_emp_shift() # Dynamic call
542
  TEAM_REQ_PER_PRODUCT = get_team_requirements()
543