CoolFace
Apppublic

atulyamann/flexup-api

sourceHugging Facemitupdated 3mo agoView on Hugging Face
0likes
stitched.py663 linesDownload Raw Back to root
1 2import pandas as pd3import numpy as np4from datetime import datetime5 6# ============================================================7# DATE RANGE CONFIGURATION 8# ============================================================9start_date = pd.to_datetime('05/31/2026', format='%m/%d/%Y')10end_date = pd.to_datetime('08/16/2026', format='%m/%d/%Y')11 12# Generate date range13date_range = pd.date_range(start=start_date, end=end_date, freq='D')14dates = [f"{dt.month}/{dt.day}" for dt in date_range]15 16print("="*70)17print("FULLY CORRECTED SCRIPT - All metrics fixed")18print("="*70)19print(f"Start Date: {start_date.strftime('%m/%d/%Y')}")20print(f"End Date: {end_date.strftime('%m/%d/%Y')}")21print(f"Total Days: {len(dates)}")22print(f"Generated Dates: {dates}")23print("="*70 + "")24def apply_date_conversion(df, sheet_name):25   """26   Convert date-like column headers (strings, Excel serial numbers, etc.)27   to pd.Timestamp objects.  Returns (modified_df, list_of_converted_cols).28   Non-date columns (e.g. 'site', 'FC', 'SU%') are left untouched.29   """30   converted = []31   new_cols = {}32   for col in df.columns:33       if isinstance(col, (pd.Timestamp, datetime)):34           converted.append(col)35           continue36       # Try parsing string dates37       if isinstance(col, str):38           for fmt in ('%m/%d/%Y', '%Y-%m-%d', '%m/%d/%y', '%m-%d-%Y', '%B %d, %Y'):39               try:40                   ts = pd.to_datetime(col, format=fmt)41                   new_cols[col] = ts42                   converted.append(ts)43                   break44               except (ValueError, TypeError):45                   continue46       # Excel serial-number dates (float/int)47       elif isinstance(col, (int, float)) and 40000 < col < 60000:48           try:49               ts = pd.Timestamp('1899-12-30') + pd.Timedelta(days=int(col))50               new_cols[col] = ts51               converted.append(ts)52           except Exception:53               pass54   if new_cols:55       df = df.rename(columns=new_cols)56   print(f"  ✓ {sheet_name}: converted {len(converted)} date columns")57   return df, converted58 59# Define all 292 sites based on the SQL query60all_sites = [61    # AR Sortable sites62    'ABQ1','ACY1','AGS1','AGS2','AKC1','ATL2','AUS2','AUS3','BDL2','BDL3','BDL4',63    'BFI4','BFL1','BHM1','BOI2','BOS3','BTR1','BWI2','CLE2','CLE3','CLT4','CMH1','CMH4','DAB2','DAL3',64    'DCA1','DEN3','DEN4','DET3','DET6','DFW7','DSM5','DTW1','ELP1','EWR4','EWR9','FAT1','FSD1','FTW6',65    'FWA6','GEG1','GRR1','GYR1','HOU2','HOU6','IGQ1','JAN1','JAX2','JFK8','LAS7','LGA9','LGB3','LGB7',66    'LIT1','LUK2','MCO1','MDW7','MEM4','MIA1','MKC6','MKE1','MKE2','MLI1','MQY1','MSP1','MTN1','OAK4',67    'OKC1','OMA2','ORD5','ORF3','ORF4','ORH3','OXR1','PAE2','PCW1','PDX8','PDX9','PSP1','PVD2','RDU1',68    'RIC4','ROC1','SAN3','SAT2','SAT3','SAV4','SBD6','SBN1','SCK6','SHV1',69    'SLC1','SMF1','STL8','SYR1','TLH2','TPA1','TPA4','TUL2','TUS2','TYS1','VGT1',70    # NonSortable sites71    'ABE4','ACY2','AKR1','ALB1','AMA1','BFI3','BNA2','BOS7','BWI4','CHA2',72    'CHO1','CLT3','CMH2','CMH3','DCA6','DEN8','DET1','DET2','DFW6','FAT2','FOE1','FTW5',73    'GEG2','GSO1','HOU8','HSV1','ICT2','IGQ2','ILG1','IND5','JAX3','JVL1','LAS6','LFT1',74    'LGB4','LGB6','LIT2','MCE1','MCO2','MDT1','MDT4','MDW6','MEM6','MGE3','MKC4','OAK3',75    'OKC2','ORD2','PDX7','PHL4','PHL5','PHL6','PHX5','PHX7','PIT2','RIC1','RNO4','SAT1',76    'SAT4','SAV3','SBD2','SCK1','SJC7','SLC2','SMF6','SNA4','STL3','STL4','SWF1','TEB3',77    'TEB4','TEB6','TPA2','TPA3',78    # Canada sites79    'YEG1','YOO1','YOW1','YVR3','YYZ2','YYZ3','YYZ9','YYZ1','YHM1','YOW3','YUL2','YXU1','YYZ4','YYZ7','YEG2','YVR2','YVR4','YXX2','YYC1','YYC4',80    # SDC sites81    'ATL7','AVP8','HGR5','KRB1','KRB2','KRB3','KRB4','KRB6','QXX6','SAV7',82    # TSSL sites83    'ABE2','CAE1','IND1','PHL7','RIC2','AFW1','BFL2','BNA3','CSG1','DAL2',84    'JAX7','MDW4','ONT2','ONT6','PHX3','RDG1','SDF2','SDF8','ABE3','FTW9','IND4','MGE1',85    # IXD sites (nIXD and rIXD)86    'ABQ2','ABS4','GEU2','GEU3','GEU5','HEA2','HGR6','HIA1','HLI2','LBE1',87    'MIT2','PBI3','POC1','POC2','POC3','PPO4','PSC2','QXY8','RYY2','TCY1','TCY2','TMB8',88    'WBW2','ABE8','AVP1','BNA6','CLT2','FTW1','FWA4','GYR2','GYR3','IAH3','IND9','LAN2',89    'LAS1','LAX9','LGB8','MCC1','MDW2','MEM1','MQJ1','ONT8','ORF2','PSP3','RDU2','RDU4',90    'RFD2','RMN3','SBD1','SCK4','SMF3','SWF2','TEB9','VGT2',91    # DG sites92    'BFI9','BWI1','CMH7','MEM2','MLB1',93    # IXD-NonSort sites94    'OLM1','PCA2','QXY4','SBD3','SCK8'95]96 97# Define the metrics98metrics = [99    'FC Shipments Planned Cap',100    'Daily MMV',101    'Daily Delta',102    'Max of % to DCM or RSP',103    'SETWEEKP',104    'MaxWeekCap',105    'METQS hrs',106    'Flex Up from SET up to 55',107    'LaborShareMax',108    'LaborSharePlan',109    'Rate',110    'Flex Up from LS gap from max',111    'RR',112    'Hires',113    'Flex Up from Hires',114    'VTO Hours',115    'Flex Up from VTO Removal',116    'VET hours',117    'Flex up from VET addition',118    'Show hours',119    'VET Ideal hours',120    'VET Flex up'121]122 123# Network and Region mapping function124def get_network_region(site):125    """Map site to Network and Region based on the SQL logic"""126    if site in ['ABQ1','ACY1','AGS1','AGS2','AKC1','ATL2','AUS2','AUS3','BDL2','BDL3','BDL4',127                'BFI4','BFL1','BHM1','BOI2','BOS3','BTR1','BWI2','CLE2','CLE3','CLT4','CMH1','CMH4','DAB2','DAL3',128                'DCA1','DEN3','DEN4','DET3','DET6','DFW7','DSM5','DTW1','ELP1','EWR4','EWR9','FAT1','FSD1','FTW6',129                'FWA6','GEG1','GRR1','GYR1','HOU2','HOU6','IGQ1','JAN1','JAX2','JFK8','LAS7','LGA9','LGB3','LGB7',130                'LIT1','LUK2','MCO1','MDW7','MEM4','MIA1','MKC6','MKE1','MKE2','MLI1','MQY1','MSP1','MTN1','OAK4',131                'OKC1','OMA2','ORD5','ORF3','ORF4','ORH3','OXR1','PAE2','PCW1','PDX8','PDX9','PSP1','PVD2','RDU1',132                'RIC4','ROC1','SAN3','SAT2','SAT3','SAV4','SBD6','SBN1','SCK6','SHV1',133                'SLC1','SMF1','STL8','SYR1','TLH2','TPA1','TPA4','TUL2','TUS2','TYS1','VGT1']:134        network = 'AR Sortable'135        if site in ['BFI9','BFI3','DEN8','GEG2','PDX7','SLC2','BFI1','BFI4','BOI2','DEN3','DEN4','GEG1','PAE2','PDX8','PDX9','SLC1','BFI7','DEN7','PDX6','SLC3']:136            region = 'NorthWest'137        elif site in ['FAT2','LAS6','LGB4','LGB6','MCE1','OAK3','PHX5','PHX7','RNO4','SBD2','SCK1','SJC7','SMF6','SNA4','KRB1','KRB3','KRB4','BFL1','FAT1','GYR1','LAS7','LGB3','LGB7','OAK4','OXR1','PSP1','SAN3','SBD6','SCK6','SMF1','TUS2','VGT1','BFL2','ONT2','ONT6','PHX3']:138            region = 'SouthWest'139        elif site in ['CMH7','AKR1','CMH2','CMH3','DET1','DET2','IND5','PIT2','CLE2','CLE3','CMH1','CMH4','DET3','DET6','DTW1','FWA6','GRR1','LUK2','PCW1','IND1','SDF1','SDF2','SDF8','IND4']:140            region = 'MidWest'141        elif site in ['FOE1','IGQ2','JVL1','MDW6','MKC4','ORD2','STL3','STL4','DSM5','FSD1','IGQ1','MDW7','MKC6','MKE1','MKE2','MLI1','MSP1','OMA2','ORD5','SBN1','STL8','MDW4']:142            region = 'GreatLakes'143        elif site in ['MEM2','BNA2','CHA2','HSV1','LFT1','LIT2','MEM6','MGE3','AGS2','ATL2','BHM1','BTR1','JAN1','LIT1','MEM4','MQY1','SHV1','BNA3','CSG1','MGE1']:144            region = 'MidSouth'145        elif site in ['AMA1','DFW6','FTW5','HOU8','ICT2','OKC2','SAT1','SAT4','ABQ1','AUS2','AUS3','DAL3','DFW7','ELP1','FTW6','HOU2','HOU6','OKC1','SAT2','SAT3','TUL2','AFW1','DAL2','FTW9']:146            region = 'Texas'147        elif site in ['ABE4','ACY2','ALB1','BOS7','PHL4','PHL5','SWF1','TEB3','TEB4','TEB6','AVP8','HGR5','QXX6','BDL2','BDL3','BDL4','BOS3','EWR4','EWR9','JFK8','LGA9','ORH3','PVD2','ROC1','SYR1','ABE2','RDG1','ABE3']:148            region = 'NorthEast'149        elif site in ['BWI1','BWI4','CHO1','CLT3','DCA6','GSO1','ILG1','MDT1','MDT4','PHL6','RIC1','KRB2','ACY1','AKC1','BWI2','CLT4','DCA1','MTN1','ORF3','ORF4','RDU1','RIC4','TYS1','CAE1','PHL7','RIC2']:150            region = 'MidAtlantic'151        elif site in ['MLB1','JAX3','MCO2','SAV3','TPA2','TPA3','ATL7','SAV7','AGS1','DAB2','JAX2','MCO1','MIA1','SAV4','TLH2','TPA1','TPA4','JAX7']:152            region = 'Florida'153        else:154            region = 'UnMapped'155    elif site in ['ABE4','ACY2','AKR1','ALB1','AMA1','BFI3','BNA2','BOS7','BWI4','CHA2','CHO1','CLT3','CMH2','CMH3','DCA6','DEN8','DET1','DET2','DFW6','FAT2','FOE1','FTW5','GEG2','GSO1','HOU8','HSV1','ICT2','IGQ2','ILG1','IND5','JAX3','JVL1','LAS6','LFT1','LGB4','LGB6','LIT2','MCE1','MCO2','MDT1','MDT4','MDW6','MEM6','MGE3','MKC4','OAK3','OKC2','ORD2','PDX7','PHL4','PHL5','PHL6','PHX5','PHX7','PIT2','RIC1','RNO4','SAT1','SAT4','SAV3','SBD2','SCK1','SJC7','SLC2','SMF6','SNA4','STL3','STL4','SWF1','TEB3','TEB4','TEB6','TPA2','TPA3']:156        network = 'NonSortable'157        region = 'NonSortable'158    elif site in ['YEG1','YOO1','YOW1','YVR3','YYZ2','YYZ3','YYZ9','YYZ1','YHM1','YOW3','YUL2','YXU1','YYZ4','YYZ7','YEG2','YVR2','YVR4','YXX2','YYC1','YYC4']:159        network = 'CANADA'160        if site in ['YEG2','YVR2','YVR4','YXX2','YYC1','YYC4']:161            region = 'Sortable-West'162        elif site in ['YHM1','YOW3','YUL2','YXU1','YYZ4','YYZ7']:163            region = 'Sortable-East'164        elif site == 'YYZ1':165            region = 'CA-Returns'166        else:167            region = 'NonSortable'168    elif site in ['ATL7','AVP8','HGR5','KRB1','KRB2','KRB3','KRB4','KRB6','QXX6','SAV7']:169        network = 'SDC'170        region = 'SDC'171    elif site in ['ABE2','CAE1','IND1','PHL7','RIC2','AFW1','BFL2','BNA3','CSG1','DAL2','JAX7','MDW4','ONT2','ONT6','PHX3','RDG1','SDF2','SDF8','ABE3','FTW9','IND4','MGE1']:172        network = 'TSSL'173        region = 'TSSL'174    elif site in ['ABQ2','ABS4','GEU2','GEU3','GEU5','HEA2','HGR6','HIA1','HLI2','LBE1','MIT2','PBI3','POC1','POC2','POC3','PPO4','PSC2','QXY8','RYY2','TCY1','TCY2','TMB8','WBW2','ABE8','AVP1','BNA6','CLT2','FTW1','FWA4','GYR2','GYR3','IAH3','IND9','LAN2','LAS1','LAX9','LGB8','MCC1','MDW2','MEM1','MQJ1','ONT8','ORF2','PSP3','RDU2','RDU4','RFD2','RMN3','SBD1','SCK4','SMF3','SWF2','TEB9','VGT2']:175        network = 'IXD'176        if site in ['ABQ2','ABS4','GEU2','GEU3','GEU5','HEA2','HGR6','HIA1','HLI2','LBE1','MIT2','PBI3','POC1','POC2','POC3','PPO4','PSC2','QXY8','RYY2','TCY1','TCY2','TMB8','WBW2']:177            region = 'NIXD'178        else:179            region = 'RIXD'180    elif site in ['BFI9','BWI1','CMH7','MEM2','MLB1']:181        network = 'DG'182        region = 'DG'183    elif site in ['OLM1','PCA2','QXY4','SBD3','SCK8']:184        network = 'IXD-NonSort'185        region = 'IXD-NonSort'186    else:187        network = 'UnMapped'188        region = 'UnMapped'189    190    return network, region191 192# Load all source sheets193print("Loading source data sheets from test3.xlsx...")194 195try:196    source_file = r"C:\Users\atulyam\Desktop\Flexup\test7tf.xlsx"197    198    pc_df = pd.read_excel(source_file, sheet_name='PC')199    mmv_df = pd.read_excel(source_file, sheet_name='MMV')200    ls_df = pd.read_excel(source_file, sheet_name='LS')201    max_ls_df = pd.read_excel(source_file, sheet_name='MAX LS')202    su_df = pd.read_excel(source_file, sheet_name='SU')203    vto_df = pd.read_excel(source_file, sheet_name='VTO')204    vet_df = pd.read_excel(source_file, sheet_name='VET')205    show_df = pd.read_excel(source_file, sheet_name='Show')206    met_hrs_df = pd.read_excel(source_file, sheet_name='MET hrs')  # ← ADDED THIS LINE207    metqs_df = pd.read_excel(source_file, sheet_name='METQS hrs')208    rate_df    = pd.read_excel(source_file, sheet_name='Rate')   # ← NEW: load Rate tab209    hires_df = pd.read_excel(source_file, sheet_name='Hires')  # ← FIX: was missing210    rr_raw_df = pd.read_excel(source_file, sheet_name='RR')211    print("✓ All source sheets loaded successfully")212    for drop_col in ['Concept', 'Shift', 'Flow']:213        if drop_col in metqs_df.columns:214           metqs_df = metqs_df.drop(columns=[drop_col])215    for drop_col in ['Concept', 'Aspect', 'Flow', 'Shift']:216        if drop_col in met_hrs_df.columns:217            met_hrs_df = met_hrs_df.drop(columns=[drop_col])218    for drop_col in ['Concept', 'Aspect', 'Flow', 'Shift', 'concept', 'aspect', 'flow', 'shift']:219        if drop_col in met_hrs_df.columns:220            met_hrs_df = met_hrs_df.drop(columns=[drop_col])221    # ── NORMALIZE DATE COLUMNS ON EVERY SHEET ────────────────────────────────222    # apply_date_conversion() converts string/serial date headers → pd.Timestamp223    # so that date_mapping lookups work correctly across all sheets224    pc_df,      _ = apply_date_conversion(pc_df,      'PC')225    ls_df,      _ = apply_date_conversion(ls_df,      'LS')226    vto_df,     _ = apply_date_conversion(vto_df,     'VTO')227    vet_df,     _ = apply_date_conversion(vet_df,     'VET')228    show_df,    _ = apply_date_conversion(show_df,    'Show')229    met_hrs_df, _ = apply_date_conversion(met_hrs_df, 'MET hrs')230    metqs_df, _ = apply_date_conversion(metqs_df, 'METQS hrs')231    rate_df,    _ = apply_date_conversion(rate_df,    'Rate')232    hires_df,   _ = apply_date_conversion(hires_df,   'Hires')233    # Add this after loading su_df234    su_df, _ = apply_date_conversion(su_df, 'SU')  # Convert date columns to Timestamps235    su_lookup = su_df.set_index('site')  # Create lookup indexed by site236    # Note: mmv_df, max_ls_df, su_df don't use date columns as headers — skip them237 238    print("✓ Date columns normalized on all sheets")239    # ── PROCESS RR DATA: DIVIDE RUN RATE BY 7 AND SPREAD ACROSS WEEK ─────────240    # Convert 'Run Week' to datetime (week start date)241    rr_raw_df['Run Week'] = pd.to_datetime(rr_raw_df['Run Week'], format='%m/%d/%Y')242 243    # Create a row for each day of the week (7 days) with daily_rr = Run Rate / 7244    rr_expanded = []245    for _, row in rr_raw_df.iterrows():246        week_start = row['Run Week']247        daily_rr   = row['Run Rate'] / 7248        site       = row['Site']249 250        # Generate 7 days starting from week_start251        for day_offset in range(7):252            day_date = week_start + pd.Timedelta(days=day_offset)253            rr_expanded.append({254                'site': site,255                'date': day_date,256                'daily_rr': daily_rr257            })258 259    rr_daily_df = pd.DataFrame(rr_expanded)260 261    # Pivot: rows = site, columns = date, values = daily_rr262    rr_pivot = rr_daily_df.pivot_table(263        index='site',264        columns='date',265        values='daily_rr',266        aggfunc='sum'  # sum in case multiple rows exist for same site+date267    ).fillna(0)268 269    print(f"✓ RR data processed: {len(rr_pivot)} sites × {len(rr_pivot.columns)} dates")270 271    # ── BUILD DATE MAPPING FROM PC SHEET ─────────────────────────────────────272    # After conversion, PC date columns are pd.Timestamp objects273    date_cols_pc = [col for col in pc_df.columns if isinstance(col, (pd.Timestamp, datetime))]274    print(f"✓ Found {len(date_cols_pc)} datetime columns in PC sheet")275 276   277    278   279    # Create date mapping280    date_mapping = {}281    for dt_col in date_cols_pc:282        date_str = f"{dt_col.month}/{dt_col.day}"283        date_mapping[date_str] = dt_col284    285    # Validate dates286    missing_dates = [d for d in dates if d not in date_mapping]287    if missing_dates:288        print(f"⚠ WARNING: Missing dates: {missing_dates}")289        dates = [d for d in dates if d in date_mapping]290    291    print(f"✓ Processing {len(dates)} dates")292    293     # Set up lookup dataframes294    pc_lookup = pc_df.set_index('site')295    ls_lookup = ls_df.set_index('site')296    vto_lookup = vto_df.set_index('site')297    vet_lookup = vet_df.set_index('site')298    show_lookup = show_df.set_index('site')299    met_hrs_lookup = met_hrs_df.set_index('site')  # ← ADDED THIS LINE300    metqs_lookup = metqs_df.set_index('site')301    rate_lookup    = rate_df.set_index('site')   # ← NEW: Rate tab lookup302    hires_lookup = hires_df.set_index('site')303    rr_lookup      = rr_pivot  # ← NEW: RR lookup (already indexed by site)304 305    306    print("✓ Lookup dataframes created")307    print("=== METQS DEBUG ===")308    print(f"metqs_lookup index (sites): {metqs_lookup.index[:5].tolist()}")309    print(f"metqs_lookup columns: {metqs_lookup.columns.tolist()}")310    print(f"PC lookup columns (sample): {[c for c in pc_lookup.columns if isinstance(c, pd.Timestamp)][:5]}")311    print(f"Test site in metqs: {'AUS3' in metqs_lookup.index}")312    if 'AUS3' in metqs_lookup.index:313       print(f"metqs row for AUS3: {metqs_lookup.loc['AUS3'].head(10)}")314except Exception as e:315    print(f"Error loading source file: {e}")316    raise317def create_site_metrics(site_code):318    """Create a DataFrame with calculated metrics for a given site - FULLY CORRECTED VERSION"""319    network, region = get_network_region(site_code)320    321    # Pre-process MMV: build a dict of {week_start_date: weekly_mmv / 7}322    mmv_site_data = mmv_df[mmv_df['FC'] == site_code]323    mmv_weekly = {}324    if len(mmv_site_data) > 0:325        for col in mmv_site_data.columns:326            if col == 'FC':327                continue328            try:329                col_date = pd.to_datetime(col)330                val = mmv_site_data.iloc[0][col]331                if pd.notna(val) and val != '':332                    mmv_weekly[col_date] = float(val) / 7333            except:334                continue335    def get_daily_mmv(dt_col):336        """Find which week this date belongs to and return that week's MMV / 7"""337        dt_date = pd.to_datetime(dt_col)338        for week_start, daily_val in mmv_weekly.items():339            week_end = week_start + pd.Timedelta(days=6)340            if week_start <= dt_date <= week_end:341                return daily_val342        return 0343    site_data = []344    345    for metric in metrics:346        row = {347            'Network': network,348            'Region': region,349            'Site': site_code,350            'Metric': metric351        }352        353        for date_str in dates:354            try:355                dt_col = date_mapping.get(date_str)356                357                if dt_col is None:358                    value = ''359                elif metric == 'FC Shipments Planned Cap':360                    value = pc_lookup.loc[site_code, dt_col] if site_code in pc_lookup.index else ''361                elif metric == 'Daily MMV':362                    value = get_daily_mmv(dt_col) if get_daily_mmv(dt_col) > 0 else ''363                elif metric == 'Daily Delta':364                    if site_code in pc_lookup.index and get_daily_mmv(dt_col) > 0:365                        value = get_daily_mmv(dt_col) - pc_lookup.loc[site_code, dt_col]366                    else:367                        value = ''368                elif metric == 'Max of % to DCM or RSP':369                   if site_code in pc_lookup.index and get_daily_mmv(dt_col) > 0:370                       fc_val = pc_lookup.loc[site_code, dt_col]371                       dcm_ratio = fc_val / get_daily_mmv(dt_col) if get_daily_mmv(dt_col) != 0 else 0372 373                       # Get RSP from SU sheet (station percentage per day)374                       # SU sheet now has date columns with station_percentage values375                       try:376                           if site_code in su_lookup.index and dt_col in su_lookup.columns:377                              rsp_val = su_lookup.loc[site_code, dt_col]378                              # Convert from percentage (e.g., 36.11) to ratio (0.3611)379                              if not pd.isna(rsp_val) and rsp_val != '':380                                 rsp_ratio = rsp_val / 100381                              else:382                                 rsp_ratio = 0383                           else:384                                rsp_ratio = 0385                       except:386                           rsp_ratio = 0387 388                       value = max(dcm_ratio, rsp_ratio)389                   else:390                     value = ''391                elif metric == 'SETWEEKP':392                    # ← MODIFIED THIS SECTION TO USE MET HRS LOOKUP393                    value = met_hrs_lookup.loc[site_code, dt_col] if site_code in met_hrs_lookup.index else 0394                elif metric == 'MaxWeekCap':395                    value = 55396                elif metric == 'METQS hrs':397                    value = metqs_lookup.loc[site_code, dt_col] if site_code in metqs_lookup.index and dt_col in metqs_lookup.columns else ''398                elif metric == 'Flex Up from SET up to 55':399                    if site_code in pc_lookup.index and get_daily_mmv(dt_col) > 0:400                        fc_val = pc_lookup.loc[site_code, dt_col]401                        delta = get_daily_mmv(dt_col) - fc_val402                        # Get SETWEEKP from MET hrs lookup403                        met_val = metqs_lookup.loc[site_code, dt_col] if site_code in metqs_lookup.index  else 40404                        #and dt_col in metqs_lookup.columns405                        if met_val == '' or (isinstance(met_val, float) and pd.isna(met_val)):406                           met_val = 40407                        met_val = float(met_val)408                        max_week_cap = 55409                        # New formula: (MET hours / MaxWeekCap) * FC - FC410                        max_flex = (max_week_cap / met_val) * fc_val-fc_val  if met_val != 0 else 0411                        value = max(0, min(delta, max_flex))412                    else:413                        value = ''414                elif metric == 'LaborShareMax':415                    try:416                        current_date = pd.to_datetime(f"{date_str}/2026", format='%m/%d/%Y')417                        matching_rows = max_ls_df[418                            (max_ls_df['site'] == site_code) &419                            (max_ls_df['start_date'] == current_date)420                        ]421                        value = matching_rows['input_value'].sum()422                        value = '' if value == 0 else value423                    except:424                        value = ''425                elif metric == 'LaborSharePlan':426                    value = ls_lookup.loc[site_code, dt_col] if site_code in ls_lookup.index else ''427                elif metric == 'Rate':428                    # ── CHANGED: pull directly from Rate tab (rate_lookup), same pattern as shw_hrs ──429                    # Previously: Rate = FC Shipments Planned Cap / Show hours (calculated)430                    # Now:        Rate = direct lookup from test3.xlsx → Rate sheet, indexed by site431                    value = rate_lookup.loc[site_code, dt_col] if site_code in rate_lookup.index else ''432                elif metric == 'Flex Up from LS gap from max':433                    if site_code in pc_lookup.index and site_code in ls_lookup.index and get_daily_mmv(dt_col) > 0:434                        fc_val = pc_lookup.loc[site_code, dt_col]435                        delta = get_daily_mmv(dt_col) - fc_val436                        try:437                            current_date = pd.to_datetime(f"{date_str}/2026", format='%m/%d/%Y')438                            matching_rows = max_ls_df[439                                (max_ls_df['site'] == site_code) &440                                (max_ls_df['start_date'] == current_date)441                            ]442                            ls_max = matching_rows['input_value'].sum()443                        except:444                            ls_max = 0445                        ls_plan = ls_lookup.loc[site_code, dt_col]446                        if site_code in show_lookup.index:447                            show_val = show_lookup.loc[site_code, dt_col]448                            rate = fc_val / show_val if show_val != 0 and show_val != '' and not pd.isna(show_val) else 0449                        else:450                            rate = 0451                        ls_gap_flex = (ls_max - ls_plan) * rate452                        value = max(0, min(delta, ls_gap_flex)) if ls_max > 0 else ''453                    else:454                        value = ''455                elif metric == 'RR':456                    # ── NEW: RR lookup from processed RR data (daily_rr = Run Rate / 7) ──457                    # rr_lookup is indexed by site, columns are pd.Timestamp dates458                    value = rr_lookup.loc[site_code, dt_col] if site_code in rr_lookup.index and dt_col in rr_lookup.columns else ''459                elif metric == 'Hires':460                    # ── NEW: direct lookup from Hires tab, same pattern as shw_hrs / show_lookup ──461                    # =INDEX(Hires!..., MATCH(site,...,0), MATCH(date,...,0))462                    value = hires_lookup.loc[site_code, dt_col] if site_code in hires_lookup.index else ''463                elif metric == 'Flex Up from Hires':464                    if (site_code in pc_lookup.index and465                        site_code in hires_lookup.index and466                        site_code in rate_lookup.index and467                        site_code in rr_lookup.index and468                        get_daily_mmv(dt_col) > 0):469 470                        fc_val = pc_lookup.loc[site_code, dt_col]471                        delta  = get_daily_mmv(dt_col) - fc_val472                        rate   = rate_lookup.loc[site_code, dt_col]473 474                        # ── Determine the week start (Sunday) for this date ──475                        dt_date = pd.to_datetime(dt_col)476                        dow = dt_date.dayofweek  # Monday=0, Sunday=6477                        week_start = dt_date - pd.Timedelta(days=(dow + 1) % 7)478                        week_end = week_start + pd.Timedelta(days=6)479 480                        # ── Sum Hires across the whole week ──481                        weekly_hires = 0482                        for wc in hires_lookup.columns:483                            try:484                                wc_date = pd.to_datetime(wc)485                                if week_start <= wc_date <= week_end:486                                   h = hires_lookup.loc[site_code, wc]487                                   if pd.notna(h) and h != '':488                                      weekly_hires += float(h)489                            except:490                               continue491 492                        # ── Get weekly RR from raw data (not divided by 7) ──493                        rr_val = 0494                        dt_date = pd.to_datetime(dt_col)495                        rr_match = rr_raw_df[496                            (rr_raw_df['Site'] == site_code) &497                            (rr_raw_df['Run Week'] <= dt_date) &498                            (rr_raw_df['Run Week'] + pd.Timedelta(days=6) >= dt_date)499                        ]500                        if len(rr_match) > 0:501                            rr_val = float(rr_match['Run Rate'].iloc[0])502 503                        # Guard rate504                        if rate == '' or (isinstance(rate, float) and pd.isna(rate)):505                           rate = 0506                        rate = float(rate)507 508                        # ── DEBUG: uncomment to see values ── 509                        #print(f"Site={site_code}, Date={dt_col}, RR={rr_val}, Weekly_Hires={weekly_hires}, Rate={rate}, Delta={delta}")510 511                        # ── (Weekly_RR - Weekly_Hires) × Rate / 7 ──512                        flex_hires = (rr_val - weekly_hires) * rate / 7513                        value = max(0, min(delta, flex_hires))514                    else:515                       value = ''516 517                elif metric == 'VTO Hours':518                    value = vto_lookup.loc[site_code, dt_col] if site_code in vto_lookup.index else ''519                elif metric == 'Flex Up from VTO Removal':520                    if site_code in pc_lookup.index and site_code in ls_lookup.index and site_code in vto_lookup.index and get_daily_mmv(dt_col) > 0:521                        fc_val = pc_lookup.loc[site_code, dt_col]522                        delta = get_daily_mmv(dt_col) - fc_val523                        vto_val = vto_lookup.loc[site_code, dt_col]524                        ls_val = ls_lookup.loc[site_code, dt_col]525                        rate = rate_lookup.loc[site_code, dt_col]526                         # Guard against NaN / empty527                        if vto_val == '' or (isinstance(vto_val, float) and pd.isna(vto_val)):528                            vto_val = 0529                        if rate == '' or (isinstance(rate, float) and pd.isna(rate)):530                            rate = 0531 532                        vto_val = float(vto_val)533                        rate = float(rate)534                        value = max(0, min(delta, vto_val * rate))535                    else:536                        value = ''537                elif metric == 'VET hours':538                    value = vet_lookup.loc[site_code, dt_col] if site_code in vet_lookup.index else ''539                elif metric == 'Flex up from VET addition':540                     if site_code in pc_lookup.index and site_code in show_lookup.index and site_code in vet_lookup.index and get_daily_mmv(dt_col) > 0:541                         # Get SETWEEKP first — skip if 0542                         setweekp = met_hrs_lookup.loc[site_code, dt_col] if site_code in met_hrs_lookup.index else 0543                         if setweekp == '' or (isinstance(setweekp, float) and pd.isna(setweekp)):544                            setweekp = 0545                         setweekp = float(setweekp)546 547                         #if 548                         #setweekp == 0:549                           #value = ''550                           #else:551                         fc_val = pc_lookup.loc[site_code, dt_col]552                         delta = get_daily_mmv(dt_col) - fc_val553                         vet_val = vet_lookup.loc[site_code, dt_col]554                         show_val = show_lookup.loc[site_code, dt_col]555 556                         if show_val != '' and not pd.isna(show_val):557                                vet_ideal = 0 if setweekp > 0 else show_val * 0.1558                         else:559                                 vet_ideal = 0560 561                         if vet_val != '' and not pd.isna(vet_val):562                                vet_flex_up = max(0, vet_ideal - vet_val)563                         else:564                                vet_flex_up = vet_ideal565 566                         rate = rate_lookup.loc[site_code, dt_col]567                         vet_capacity = vet_flex_up * rate568                         value = max(0, min(delta, vet_capacity))569 570                     else:571                        value = ''572                elif metric == 'Show hours':573                    value = show_lookup.loc[site_code, dt_col] if site_code in show_lookup.index else ''574                elif metric == 'VET Ideal hours':575                    if site_code in show_lookup.index:576                        show_val = show_lookup.loc[site_code, dt_col]577                        setweekp = met_hrs_lookup.loc[site_code, dt_col] if site_code in met_hrs_lookup.index else 0578                        if setweekp == '' or (isinstance(setweekp, float) and pd.isna(setweekp)):579                             setweekp = 0580                             setweekp = float(setweekp) 581                        #setweekp = 40582                        if show_val != '' and not pd.isna(show_val):583                            value = 0 if setweekp > 0 else show_val * 0.1584                        else:585                            value = ''586                    else:587                        value = ''588                elif metric == 'VET Flex up':589                    if site_code in vet_lookup.index and site_code in show_lookup.index:590                       setweekp = met_hrs_lookup.loc[site_code, dt_col] if site_code in met_hrs_lookup.index else 0591                       if setweekp == '' or (isinstance(setweekp, float) and pd.isna(setweekp)):592                          setweekp = 0593                       setweekp = float(setweekp)594 595                       #if setweekp == 0:596                        #   value = ''597                       #else:598                       vet_val = vet_lookup.loc[site_code, dt_col]599                       show_val = show_lookup.loc[site_code, dt_col]600                       if show_val != '' and not pd.isna(show_val) and vet_val != '' and not pd.isna(vet_val):601                              vet_ideal = 0 if setweekp > 0 else show_val * 0.1602                              value = 0 if vet_val > vet_ideal else vet_ideal - vet_val603                       else:604                              value = ''605                    else:606                             value = ''607                else:608                    value = ''609                    610            except Exception as e:611                value = ''612            613            row[date_str] = value614        615        site_data.append(row)616    617    return pd.DataFrame(site_data)618# Temporary debug — remove after confirming619test_site = 'AUS3'620test_date = date_mapping.get('5/31')621print(f"=== DEBUG: Testing lookups for {test_site} on 3/8 ===")622print(f"met_hrs_lookup columns: {met_hrs_lookup.columns.tolist()[:10]}")623print(f"met_hrs value: {met_hrs_lookup.loc[test_site, test_date] if test_site in met_hrs_lookup.index else 'NOT FOUND'}")624print(f"PC value:    {pc_lookup.loc[test_site, test_date] if test_site in pc_lookup.index else 'NOT FOUND'}")625print(f"Rate value:  {rate_lookup.loc[test_site, test_date] if test_site in rate_lookup.index else 'NOT FOUND'}")626print(f"Hires value: {hires_lookup.loc[test_site, test_date] if test_site in hires_lookup.index else 'NOT FOUND'}")627print(f"Show value:  {show_lookup.loc[test_site, test_date] if test_site in show_lookup.index else 'NOT FOUND'}")628# Generate for all sites629print(f"Processing {len(all_sites)} sites...")630all_sites_data = []631 632for i, site in enumerate(all_sites, 1):633    if i % 50 == 0:634        print(f"  Processed {i}/{len(all_sites)} sites...")635    site_df = create_site_metrics(site)636    all_sites_data.append(site_df)637 638combined_df = pd.concat(all_sites_data, ignore_index=True)639 640print("" + "="*60)641print("CORRECTED DataFrame Created:")642print(f"Total sites: {len(all_sites)}")643print(f"Total rows: {len(combined_df)} ({len(metrics)} metrics × {len(all_sites)} sites)")644 645# Count non-empty values646non_empty_count = 0647for date in dates:648    non_empty_count += combined_df[date].astype(str).str.strip().ne('').sum()649 650print(f"✓ Total non-empty metric values: {non_empty_count:,}")651# Save to Excel652output_file = r"C:\Users\atulyam\Desktop\Flexup\demotest48.xlsx"653with pd.ExcelWriter(output_file, engine='openpyxl') as writer:654    combined_df.to_excel(writer, sheet_name='SiteLevel(2)', index=False)655 656print(f"✓ Excel file created: {output_file}")657print(f"✓ Contains {len(all_sites)} sites with {len(metrics)} metrics each")658print(f"✓ Total rows: {len(combined_df)}")659print(f"✓ All formulas have been replicated and calculated")660print(f"Network breakdown:")661print(combined_df.groupby('Network')['Site'].nunique())662print("="*60)663