File size: 27,072 Bytes
02fd3ca
29608b7
 
 
 
de19c07
02fd3ca
26ebf77
 
 
 
 
 
 
02fd3ca
29608b7
131af7c
 
 
5afa2a4
 
131af7c
5afa2a4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
131af7c
 
 
 
26ebf77
131af7c
 
 
 
29608b7
 
 
 
 
5afa2a4
 
26ebf77
 
 
e542954
709359a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29608b7
 
 
26ebf77
29608b7
26ebf77
 
 
 
 
 
 
 
 
 
 
 
 
89d7197
e892a6b
 
29608b7
 
26ebf77
29608b7
26ebf77
 
 
 
 
29608b7
26ebf77
 
 
 
 
 
 
 
 
 
131af7c
 
26ebf77
 
 
 
 
 
 
 
 
 
 
 
 
 
de19c07
 
131af7c
26ebf77
 
131af7c
26ebf77
 
 
 
 
 
 
 
 
 
de19c07
26ebf77
 
 
 
e892a6b
 
29608b7
02fd3ca
51181a6
26ebf77
51181a6
26ebf77
 
 
 
 
51181a6
26ebf77
 
 
 
 
 
 
51181a6
e892a6b
 
51181a6
26ebf77
 
 
 
 
 
 
de19c07
 
e542954
de19c07
 
26ebf77
 
 
 
 
 
 
 
 
 
 
 
 
de19c07
26ebf77
 
 
 
e542954
 
26ebf77
 
 
 
 
 
51181a6
 
131af7c
 
 
 
 
51181a6
131af7c
 
 
 
 
 
 
51181a6
e892a6b
 
51181a6
709359a
 
 
 
 
e542954
709359a
 
 
 
 
 
 
 
 
 
e542954
709359a
 
 
51181a6
709359a
 
51181a6
 
 
131af7c
 
 
 
 
51181a6
131af7c
 
 
 
de19c07
42b5ea5
 
de19c07
51181a6
131af7c
 
de19c07
131af7c
e892a6b
 
51181a6
 
 
 
 
 
 
 
08284a1
 
c21f7f2
 
 
 
709359a
c21f7f2
 
 
 
709359a
 
 
51181a6
c21f7f2
 
 
 
51181a6
c21f7f2
08284a1
c21f7f2
5afa2a4
 
 
 
 
c21f7f2
 
 
 
 
 
 
709359a
5afa2a4
c21f7f2
 
5afa2a4
c21f7f2
 
 
 
08284a1
5afa2a4
 
 
c21f7f2
 
26ebf77
c21f7f2
 
08284a1
709359a
 
179d6f0
 
42b5ea5
 
131af7c
 
 
 
 
42b5ea5
131af7c
 
 
 
 
 
 
 
 
42b5ea5
131af7c
 
42b5ea5
e892a6b
 
42b5ea5
179d6f0
cd87ae5
 
709359a
 
 
 
 
 
 
 
 
 
 
 
e892a6b
 
709359a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c21f7f2
 
131af7c
 
 
 
 
c21f7f2
131af7c
 
 
 
 
c21f7f2
 
e892a6b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179d6f0
26ebf77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
709359a
 
 
 
 
 
 
 
 
 
 
 
e892a6b
 
46e0ea9
 
89d7197
 
 
 
 
 
 
 
 
868114c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e892a6b
 
868114c
42b5ea5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
de19c07
42b5ea5
 
 
e892a6b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42b5ea5
e892a6b
 
 
 
 
 
 
 
42b5ea5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
import pandas as pd
import src.etl.transform as transformed_data
import datetime
from datetime import timedelta
import src.etl.extract as extract
from src.config.constants import ShiftType, LineType, KitLevel, DefaultConfig

# Re-import all the packages
import importlib

# Reload modules to get latest changes
importlib.reload(extract)
importlib.reload(transformed_data)  # Uncomment if needed


def get_date_span():
    try:
        # Try to get from streamlit session state (from config page)
        import streamlit as st
        if hasattr(st, 'session_state') and 'start_date' in st.session_state:
            from datetime import datetime, timedelta
            start_date = datetime.combine(st.session_state.start_date, datetime.min.time())
            
            # Check if we have calculated planning_days, otherwise determine from data
            if 'planning_days' in st.session_state and st.session_state.planning_days:
                planning_days = st.session_state.planning_days
                end_date = start_date + timedelta(days=planning_days - 1)
            else:
                # Determine date range from actual demand data for the exact start date
                try:
                    demand_data = extract.read_orders_data(start_date=start_date)
                    if not demand_data.empty:
                        import pandas as pd
                        # Get unique finish dates for this exact start date
                        finish_dates = pd.to_datetime(demand_data["Basic finish date"]).dt.date.unique()
                        finish_dates = sorted(finish_dates)
                        if finish_dates:
                            end_date = datetime.combine(max(finish_dates), datetime.min.time())
                            planning_days = (end_date - start_date).days + 1
                        else:
                            end_date = start_date
                            planning_days = 1
                    else:
                        end_date = start_date + timedelta(days=4)  # Default 5 days
                        planning_days = 5
                except Exception as e:
                    print(f"Could not determine date range from data: {e}")
                    end_date = start_date + timedelta(days=4)  # Default 5 days
                    planning_days = 5
            
            date_span = list(range(1, planning_days + 1))
            print(f"Using dates from config page: {start_date} to {end_date} ({planning_days} days)")
            print("date span", date_span)
            return date_span, start_date, end_date
    except Exception as e:
        print(f"Could not get dates from streamlit session: {e}")
    
    print(f"Loading default date values")
    # Default to match the user's data in COOIS_Released_Prod_Orders.csv
    from datetime import datetime
    return list(range(1, 6)), datetime(2025, 7, 7), datetime(2025, 7, 11)  # Default 5 days


#fetch date from streamlit or default value. The streamlit and default references the demand data (COOIS_Planned_and_Released.csv)
DATE_SPAN, start_date, end_date = get_date_span() 

# Update global dates in extract module BEFORE any data loading
extract.set_global_dates(start_date, end_date)

print(f"\nπŸ“… DATE RANGE: {start_date} to {end_date}")
print(f"πŸ“ PRODUCT SOURCE: COOIS_Released_Prod_Orders.csv")

def get_product_list():
    """
    Get filtered product list.
    IMPORTANT: This dynamically loads data to reflect current Streamlit configs/dates.
    """
    try:
        # Always get fresh filtered products to reflect current configs
        from src.demand_filtering import get_shared_filter_instance
        filter_instance = get_shared_filter_instance()
        
        # Force reload data to pick up new dates/configs
        filter_instance.load_data(force_reload=True)
        
        product_list = filter_instance.get_filtered_product_list()
        print(f"πŸ“¦ FRESH FILTERED PRODUCTS: {len(product_list)} products ready for optimization")
        print(f"🎯 Products: {product_list}")
        return product_list
    except Exception as e:
        print(f"Error loading dynamic product list: {e}")
        # Fallback to unfiltered list
        product_list = transformed_data.get_released_product_list(start_date)
        print(f"πŸ“¦ FALLBACK UNFILTERED PRODUCTS: {len(product_list)} products -> {product_list}")
        return product_list

# DO NOT load at import time - always call get_product_list() dynamically
# PRODUCT_LIST = get_product_list()  # REMOVED - was causing stale data!


def get_employee_type_list():
    """Get employee type list - try from streamlit session state first, then from data files"""
    try:
        # Try to get from streamlit session state (from Dataset Metadata page)
        import streamlit as st
        if hasattr(st, 'session_state') and 'selected_employee_types' in st.session_state:
            print(f"Using employee types from Dataset Metadata page: {st.session_state.selected_employee_types}")
            return st.session_state.selected_employee_types
    except Exception as e:
        print(f"Could not get employee types from streamlit session: {e}")
    
    # Default: load from data files
    print(f"Loading employee type list from data files")
    employee_type_list = extract.read_employee_data()
    emp_type_list = employee_type_list["employment_type"].unique()
    return emp_type_list
        
# DO NOT load at import time - always call get_employee_type_list() dynamically
# EMPLOYEE_TYPE_LIST = get_employee_type_list()  # REMOVED - was causing stale data!

def get_shift_list():
    """Get shift list - try from streamlit session state first, then from data files"""
    try:
        # Try to get from streamlit session state (from Dataset Metadata page)
        import streamlit as st
        if hasattr(st, 'session_state') and 'selected_shifts' in st.session_state:
            print(f"Using shifts from Dataset Metadata page: {st.session_state.selected_shifts}")
            return st.session_state.selected_shifts
    except Exception as e:
        print(f"Could not get shifts from streamlit session: {e}")
    
    # Default: load from data files
    print(f"Loading shift list from data files")
    shift_list = extract.get_shift_info()
    shift_list = shift_list["id"].unique()
    return shift_list

# Evening shift activation mode - define early to avoid circular dependency
# Options:
#   "normal" - Only use regular shift (1) and overtime shift (3) - NO evening shift
#   "activate_evening" - Allow evening shift (2) when demand is too high or cost-effective
#   "always_available" - Evening shift always available as option
EVENING_SHIFT_MODE = "normal"  # Default: only regular + overtime

# Evening shift activation threshold
# If demand cannot be met with regular + overtime, suggest evening shift activation
EVENING_SHIFT_DEMAND_THRESHOLD = 0.9  # Activate if regular+overtime capacity < 90% of demand

def get_active_shift_list():
    """
    Get the list of active shifts based on EVENING_SHIFT_MODE setting.
    """
    all_shifts = get_shift_list()
    
    if EVENING_SHIFT_MODE == "normal":
        # Only regular and overtime shifts - NO evening shift
        active_shifts = [s for s in all_shifts if s in ShiftType.REGULAR_AND_OVERTIME]
        print(f"[SHIFT MODE] Normal mode: Using shifts {active_shifts} (Regular + Overtime only, NO evening)")
        
    elif EVENING_SHIFT_MODE == "activate_evening":
        # All shifts including evening (2)
        active_shifts = list(all_shifts)
        print(f"[SHIFT MODE] Evening activated: Using all shifts {active_shifts}")
        
    elif EVENING_SHIFT_MODE == "always_available":
        # All shifts always available
        active_shifts = list(all_shifts)
        print(f"[SHIFT MODE] Always available: Using all shifts {active_shifts}")
        
    else:
        # Default to normal mode
        active_shifts = [s for s in all_shifts if s in ShiftType.REGULAR_AND_OVERTIME]
        print(f"[SHIFT MODE] Unknown mode '{EVENING_SHIFT_MODE}', defaulting to normal: {active_shifts}")
    
    return active_shifts

# DO NOT load at import time - always call get_active_shift_list() dynamically
# SHIFT_LIST = get_active_shift_list()  # REMOVED - was causing stale data!


def get_line_list():
    """Get line list - try from streamlit session state first, then from data files"""
    try:
        # Try to get from streamlit session state (from Dataset Metadata page)
        import streamlit as st
        if hasattr(st, 'session_state') and 'selected_lines' in st.session_state:
            print(f"Using lines from Dataset Metadata page: {st.session_state.selected_lines}")
            return st.session_state.selected_lines
    except Exception as e:
        print(f"Could not get lines from streamlit session: {e}")
    
    # Default: load from data files
    print(f"Loading line list from data files")
    line_df = extract.read_packaging_line_data()
    line_list = line_df["id"].unique().tolist()
    return line_list

# DO NOT load at import time - always call get_line_list() dynamically
# LINE_LIST = get_line_list()  # REMOVED - was causing stale data!


def get_kit_line_match():
    kit_line_match = extract.read_kit_line_match_data()
    kit_line_match_dict = kit_line_match.set_index("kit_name")["line_type"].to_dict()
    
    # Create line name to ID mapping
    line_name_to_id = {
        "long line": LineType.LONG_LINE,
        "mini load": LineType.MINI_LOAD,
        "miniload": LineType.MINI_LOAD,     # Alternative naming (no space)
        "Long_line": LineType.LONG_LINE,    # Alternative naming
        "Mini_load": LineType.MINI_LOAD,    # Alternative naming
    }
    
    # Convert string line names to numeric IDs
    converted_dict = {}
    for kit, line_name in kit_line_match_dict.items():
        if isinstance(line_name, str) and line_name.strip():
            # Convert string names to numeric IDs
            line_id = line_name_to_id.get(line_name.strip(), None)
            if line_id is not None:
                converted_dict[kit] = line_id
            else:
                print(f"Warning: Unknown line type '{line_name}' for kit {kit}")
                # Default to long line if unknown
                converted_dict[kit] = LineType.LONG_LINE
        elif isinstance(line_name, (int, float)) and not pd.isna(line_name):
            # Already numeric
            converted_dict[kit] = int(line_name)
        else:
            # Missing or empty line type - skip (no production needed for non-standalone masters)
            pass  # Don't add to converted_dict - these kits won't have line assignments
            
    return converted_dict

KIT_LINE_MATCH_DICT = get_kit_line_match()


def get_line_cnt_per_type():
    try:
        # Try to get from streamlit session state (from config page)
        import streamlit as st
        if hasattr(st, 'session_state') and 'line_counts' in st.session_state:
            print(f"Using line counts from config page: {st.session_state.line_counts}")
            return st.session_state.line_counts
    except Exception as e:
        print(f"Could not get line counts from streamlit session: {e}")
    
    print(f"Loading default line count values from data files")
    line_df = extract.read_packaging_line_data()
    line_cnt_per_type = line_df.set_index("id")["line_count"].to_dict()
    print("line cnt per type", line_cnt_per_type)
    return line_cnt_per_type

# DO NOT load at import time - always call get_line_cnt_per_type() dynamically
# LINE_CNT_PER_TYPE = get_line_cnt_per_type()  # REMOVED - was causing stale data!

def get_demand_dictionary(force_reload=False):
    """
    Get filtered demand dictionary. 
    IMPORTANT: This dynamically loads data to reflect current Streamlit configs/dates.
    """
    try:
        # Always get fresh filtered demand to reflect current configs
        from src.demand_filtering import get_shared_filter_instance
        filter_instance = get_shared_filter_instance()
        
        # Force reload data to pick up new dates/configs
        filter_instance.load_data(force_reload=True)
        
        demand_dictionary = filter_instance.get_filtered_demand_dictionary()
        print(f"πŸ“ˆ FRESH FILTERED DEMAND: {len(demand_dictionary)} products with total demand {sum(demand_dictionary.values())}")
        print(f"πŸ”„ LOADED DYNAMICALLY: Reflects current Streamlit configs")
        return demand_dictionary
    except Exception as e:
        print(f"Error loading dynamic demand dictionary: {e}")
        raise Exception("Demand dictionary not found with error:"+str(e))

# DO NOT load at import time - always call get_demand_dictionary() dynamically
# DEMAND_DICTIONARY = get_demand_dictionary()  # REMOVED - was causing stale data!

def get_cost_list_per_emp_shift():
    try:
        # Try to get from streamlit session state (from config page)
        import streamlit as st
        if hasattr(st, 'session_state') and 'cost_list_per_emp_shift' in st.session_state:
            print(f"Using cost list from config page: {st.session_state.cost_list_per_emp_shift}")
            return st.session_state.cost_list_per_emp_shift
    except Exception as e:
        print(f"Could not get cost list from streamlit session: {e}")
    
    print(f"Loading default cost values")
    # Default hourly rates - Important: multiple employment types with different costs
    return DefaultConfig.DEFAULT_COST_RATES

def shift_code_to_name():
    return ShiftType.get_all_names()

def line_code_to_name():
    """Convert line type IDs to readable names"""
    return LineType.get_all_names()

# DO NOT load at import time - always call get_cost_list_per_emp_shift() dynamically
# COST_LIST_PER_EMP_SHIFT = get_cost_list_per_emp_shift()  # REMOVED - was causing stale data!
        


# COST_LIST_PER_EMP_SHIFT = {  # WH_Workforce_Hourly_Pay_Scale
#     "Fixed": {1: 0, 2: 22, 3: 18},
#     "Humanizer": {1: 10, 2: 10, 3: 10},
# }







def get_team_requirements(product_list=None):
    """
    Extract team requirements from Kits Calculation CSV.
    Returns dictionary with employee type as key and product requirements as nested dict.
    """
    if product_list is None:
        product_list = get_product_list()  # Get fresh product list
        
    try:
        # Check if streamlit has this data (for future extension)
        # streamlit_team_req = dashboard.team_requirements
        # return streamlit_team_req
        pass
    except Exception as e:
        print(f"Using default value for team requirements, extracting from CSV: {e}")
        
    # Read the kits calculation data directly
    kits_df = extract.read_personnel_requirement_data()
    # kits_path = "data/real_data_excel/converted_csv/Kits__Calculation.csv"
    # kits_df = pd.read_csv(kits_path)
    print("kits_df columns:", kits_df.columns.tolist())
    print("kits_df head:", kits_df.head())
    # Initialize the team requirements dictionary
    team_req_dict = {
        "UNICEF Fixed term": {},
        "Humanizer": {}
    }
    
    # Process each product in the product list
    for product in product_list:
        print("product",product)
        print(f"Processing team requirements for product: {product}")
        product_data = kits_df[kits_df['Kit'] == product]
        print("product_data",product_data)
        if not product_data.empty:
            # Extract Humanizer and UNICEF staff requirements
            humanizer_req = product_data["Humanizer"].iloc[0]
            unicef_req = product_data["UNICEF staff"].iloc[0]
            
            # Convert to int (data is already cleaned in extract function)
            team_req_dict["Humanizer"][product] = int(humanizer_req)
            team_req_dict["UNICEF Fixed term"][product] = int(unicef_req)
        else:
            print(f"Warning: Product {product} not found in Kits Calculation data, setting requirements to 0")
            
    
    return team_req_dict

# DO NOT load at import time - always call get_team_requirements() dynamically
# TEAM_REQ_PER_PRODUCT = get_team_requirements(PRODUCT_LIST)  # REMOVED - was causing stale data!


def get_max_employee_per_type_on_day():
    try:
        # Try to get from streamlit session state (from config page)
        import streamlit as st
        if hasattr(st, 'session_state') and 'max_employee_per_type_on_day' in st.session_state:
            print(f"Using max employee counts from config page: {st.session_state.max_employee_per_type_on_day}")
            return st.session_state.max_employee_per_type_on_day
    except Exception as e:
        print(f"Could not get max employee counts from streamlit session: {e}")
    
    print(f"Loading default max employee values")
    max_employee_per_type_on_day = {
        "UNICEF Fixed term": {
            t: 8 for t in DATE_SPAN
        },
        "Humanizer": {
            t: 10 for t in DATE_SPAN
        }
    }
    return max_employee_per_type_on_day

# DO NOT load at import time - always call get_max_employee_per_type_on_day() dynamically
# MAX_EMPLOYEE_PER_TYPE_ON_DAY = get_max_employee_per_type_on_day()  # REMOVED - was causing stale data!

# available employee but for fixed in shift 1, it is mandatory employment

MAX_HOUR_PER_PERSON_PER_DAY = 14  # legal standard
def get_max_hour_per_shift_per_person():
    """Get max hours per shift per person - checks Streamlit session state first"""
    try:
        import streamlit as st
        if hasattr(st, 'session_state') and 'max_hour_per_shift_per_person' in st.session_state:
            return st.session_state.max_hour_per_shift_per_person
    except Exception as e:
        print(f"Could not get max hours per shift from session: {e}")
    
    # Fallback to default only if not configured by user
    return DefaultConfig.MAX_HOUR_PER_SHIFT_PER_PERSON

# DO NOT load at import time - always call get_max_hour_per_shift_per_person() dynamically
# MAX_HOUR_PER_SHIFT_PER_PERSON = get_max_hour_per_shift_per_person()  # REMOVED - was causing stale data!

# Removed unnecessary getter functions - use direct imports instead:
# - MAX_HOUR_PER_PERSON_PER_DAY
# - MAX_HOUR_PER_SHIFT_PER_PERSON  
# - KIT_LINE_MATCH_DICT
# - MAX_PARALLEL_WORKERS
# - EVENING_SHIFT_MODE

# Keep these complex getters that access DefaultConfig or have complex logic:
def get_evening_shift_demand_threshold():
    """Get evening shift demand threshold - checks Streamlit session state first"""
    try:
        import streamlit as st
        if hasattr(st, 'session_state') and 'evening_shift_demand_threshold' in st.session_state:
            return st.session_state.evening_shift_demand_threshold
    except Exception as e:
        print(f"Could not get evening shift threshold from session: {e}")
    
    # Fallback to default only if not configured by user
    return getattr(DefaultConfig, 'EVENING_SHIFT_DEMAND_THRESHOLD', 10000)

def get_fixed_min_unicef_per_day():
    """Get fixed minimum UNICEF staff per day - checks Streamlit session state first"""
    try:
        import streamlit as st
        if hasattr(st, 'session_state') and 'fixed_min_unicef_per_day' in st.session_state:
            return st.session_state.fixed_min_unicef_per_day
    except Exception as e:
        print(f"Could not get fixed min UNICEF from session: {e}")
    
    # Fallback to default only if not configured by user
    return getattr(DefaultConfig, 'FIXED_MIN_UNICEF_PER_DAY', {1: 1, 2: 1, 3: 1, 4: 1, 5: 1})
def get_per_product_speed():
    try:
        # Try to get from streamlit session state (from config page)
        import streamlit as st
        if hasattr(st, 'session_state') and 'per_product_speed' in st.session_state:
            print(f"Using per product speed from config page: {st.session_state.per_product_speed}")
            return st.session_state.per_product_speed
    except Exception as e:
        print(f"Could not get per product speed from streamlit session: {e}")
    
    print(f"Loading default per product speed from data files")
    per_product_speed = extract.read_package_speed_data()
    return per_product_speed


# ============================================================================
# BETTER APPROACH: Explicit module-level variables with clear documentation
# These variables provide backward compatibility while being explicit and clear
# ============================================================================

def _ensure_fresh_config():
    """
    Helper function to refresh module-level variables when configuration changes.
    Call this after updating Streamlit session state to ensure fresh values.
    """
    global PER_PRODUCT_SPEED, LINE_LIST, EMPLOYEE_TYPE_LIST, SHIFT_LIST
    global LINE_CNT_PER_TYPE, COST_LIST_PER_EMP_SHIFT, MAX_EMPLOYEE_PER_TYPE_ON_DAY
    global MAX_HOUR_PER_SHIFT_PER_PERSON, MAX_PARALLEL_WORKERS, FIXED_MIN_UNICEF_PER_DAY
    global PAYMENT_MODE_CONFIG
    
    # Refresh all cached values
    PER_PRODUCT_SPEED = get_per_product_speed()
    LINE_LIST = get_line_list()
    EMPLOYEE_TYPE_LIST = get_employee_type_list()
    SHIFT_LIST = get_active_shift_list()
    LINE_CNT_PER_TYPE = get_line_cnt_per_type()
    COST_LIST_PER_EMP_SHIFT = get_cost_list_per_emp_shift()
    MAX_EMPLOYEE_PER_TYPE_ON_DAY = get_max_employee_per_type_on_day()
    MAX_HOUR_PER_SHIFT_PER_PERSON = get_max_hour_per_shift_per_person()
    MAX_PARALLEL_WORKERS = get_max_parallel_workers()
    FIXED_MIN_UNICEF_PER_DAY = get_fixed_min_unicef_per_day()
    PAYMENT_MODE_CONFIG = get_payment_mode_config()

# Note: Module-level variables will be initialized at the end of this file
# after all functions are defined. This ensures all getter functions are available.

# ---- Kit Hierarchy for Production Ordering ----
def get_kit_hierarchy_data():
    try:
        # Try to get from streamlit first (future extension)
        # streamlit_hierarchy = dashboard.kit_hierarchy_data
        # return streamlit_hierarchy
        pass
    except Exception as e:
        print(f"Using default hierarchy data from extract: {e}")
        
    # Get hierarchy data from extract functions
    kit_levels, dependencies, priority_order = extract.get_production_order_data()
    
    return kit_levels, dependencies, priority_order

KIT_LEVELS, KIT_DEPENDENCIES, PRODUCTION_PRIORITY_ORDER = get_kit_hierarchy_data()
print(f"Kit Hierarchy loaded: {len(KIT_LEVELS)} kits, Priority order: {len(PRODUCTION_PRIORITY_ORDER)} items")

def get_max_parallel_workers():
    """Get max parallel workers - checks Streamlit session state first"""
    try:
        import streamlit as st
        if hasattr(st, 'session_state') and 'max_parallel_workers' in st.session_state:
            return st.session_state.max_parallel_workers
    except Exception as e:
        print(f"Could not get max parallel workers from session: {e}")
    
    # Fallback to default only if not configured by user
    return DefaultConfig.MAX_PARALLEL_WORKERS

# DO NOT load at import time - always call get_max_parallel_workers() dynamically
# MAX_PARALLEL_WORKERS = get_max_parallel_workers()  # REMOVED - was causing stale data!
# maximum number of workers that can work on a line at the same time


# Fixed staff constraint mode
# Options:
#   "mandatory" - Forces all fixed staff to work full hours every day (expensive, 99.7% waste)
#   "available" - Staff available up to limits but not forced (balanced approach)
#   "priority"  - Fixed staff used first, then temporary staff (realistic business model)
#   "none"      - Purely demand-driven scheduling (cost-efficient)
FIXED_STAFF_CONSTRAINT_MODE = "priority"  # Recommended: "priority" for realistic business model

def get_fixed_min_unicef_per_day():
    """
    Get fixed minimum UNICEF employees per day - try from streamlit session state first, then default
    This ensures a minimum number of UNICEF fixed-term staff are present every working day
    """
    try:
        import streamlit as st
        if hasattr(st, 'session_state') and 'fixed_min_unicef_per_day' in st.session_state:
            print(f"Using fixed minimum UNICEF per day from config page: {st.session_state.fixed_min_unicef_per_day}")
            return st.session_state.fixed_min_unicef_per_day
    except ImportError:
        pass  # Streamlit not available in CLI mode
    
    # Default value - minimum UNICEF Fixed term employees required per day
    return 2

# Set the constant for backward compatibility
# DO NOT load at import time - always call get_fixed_min_unicef_per_day() dynamically
# FIXED_MIN_UNICEF_PER_DAY = get_fixed_min_unicef_per_day()  # REMOVED - was causing stale data!


def get_payment_mode_config():
    """
    Get payment mode configuration - try from streamlit session state first, then default values
    Payment modes:
    - "bulk": If employee works any hours in shift, pay for full shift hours
    - "partial": Pay only for actual hours worked
    """
    try:
        # Try to get from streamlit session state (from Dataset Metadata page)
        import streamlit as st
        if hasattr(st, 'session_state') and 'payment_mode_config' in st.session_state:
            print(f"Using payment mode config from streamlit session: {st.session_state.payment_mode_config}")
            return st.session_state.payment_mode_config
    except Exception as e:
        print(f"Could not get payment mode config from streamlit session: {e}")
    
    # Default payment mode configuration
    print(f"Loading default payment mode configuration")
    payment_mode_config = DefaultConfig.PAYMENT_MODE_CONFIG
    
    return payment_mode_config

# DO NOT load at import time - always call get_payment_mode_config() dynamically
# PAYMENT_MODE_CONFIG = get_payment_mode_config()  # REMOVED - was causing stale data!

# ============================================================================
# INITIALIZE MODULE-LEVEL VARIABLES
# This section is at the end to ensure all functions are defined first
# ============================================================================

# Initialize with default values (will use fallback data when no Streamlit session)
PER_PRODUCT_SPEED = get_per_product_speed()
LINE_LIST = get_line_list()
EMPLOYEE_TYPE_LIST = get_employee_type_list()
SHIFT_LIST = get_active_shift_list()
LINE_CNT_PER_TYPE = get_line_cnt_per_type()
COST_LIST_PER_EMP_SHIFT = get_cost_list_per_emp_shift()
MAX_EMPLOYEE_PER_TYPE_ON_DAY = get_max_employee_per_type_on_day()
MAX_HOUR_PER_SHIFT_PER_PERSON = get_max_hour_per_shift_per_person()
MAX_PARALLEL_WORKERS = get_max_parallel_workers()
FIXED_MIN_UNICEF_PER_DAY = get_fixed_min_unicef_per_day()
PAYMENT_MODE_CONFIG = get_payment_mode_config()

print("βœ… Module-level configuration variables initialized")

# Note: These variables are initialized once at import time with default/fallback values.
# To get fresh values after changing Streamlit configuration, either:
# 1. Call the get_*() functions directly (RECOMMENDED for dynamic use)
# 2. Call _ensure_fresh_config() to refresh all module-level variables
# 3. Use importlib.reload() to reload the entire module