diff --git a/template/Footprints AI for {store_name} - Retail Media Business Case Calculations.xlsx b/template/Footprints AI for {store_name} - Retail Media Business Case Calculations.xlsx index 1de4782..c90b9e8 100644 Binary files a/template/Footprints AI for {store_name} - Retail Media Business Case Calculations.xlsx and b/template/Footprints AI for {store_name} - Retail Media Business Case Calculations.xlsx differ diff --git a/update_excel.py b/update_excel.py index 7582774..c6c08ec 100755 --- a/update_excel.py +++ b/update_excel.py @@ -124,10 +124,9 @@ def update_excel_variables(excel_path): sheet_name_mapping[sheet_name] = new_sheet_name print(f"Renamed sheet '{sheet_name}' to '{new_sheet_name}'") - # Update formulas in all sheets to reference the new sheet names - if sheet_name_mapping: - print("Updating formulas in all sheets...") - update_formulas_in_workbook(wb, sheet_name_mapping) + # Update formulas in the Graphics sheet to reference the new sheet names + if sheet_name_mapping and 'Graphics' in wb.sheetnames: + update_formula_references(wb, sheet_name_mapping) # Save the workbook wb.save(excel_path) @@ -138,224 +137,165 @@ def update_excel_variables(excel_path): print(f"Error updating Excel file: {e}") return False -def update_formulas_in_workbook(workbook, sheet_name_mapping): +def update_formula_references(workbook, sheet_name_mapping): """ - Update formulas in all sheets of the workbook to reference the new sheet names + Update formula references in the Graphics sheet to point to the renamed sheets Args: workbook: The openpyxl workbook object sheet_name_mapping: Dictionary mapping old sheet names to new sheet names """ try: - # Process all sheets in the workbook - sheets_to_process = workbook.sheetnames - print(f"Updating formulas in all sheets: {sheets_to_process}") + # Get the Graphics sheet + graphics_sheet = workbook['Graphics'] + print("Updating formula references in Graphics sheet...") - # Track total updates for reporting - total_updates = 0 + # Track the number of updates + updates_count = 0 + total_formulas = 0 - # Process each sheet - for sheet_name in sheets_to_process: - try: - # Skip sheets that were just renamed (they're now referenced by their new names) - if sheet_name in sheet_name_mapping.values(): - continue + # Create a dictionary to track which cells have been processed + processed_cells = {} + + # First pass: Identify all cells with formulas that reference the forecast sheets + formula_cells = [] + + # Iterate through all cells in the Graphics sheet + for row in graphics_sheet.iter_rows(): + for cell in row: + # Check if the cell contains a formula + if cell.data_type == 'f' and cell.value and isinstance(cell.value, str): + total_formulas += 1 + original_formula = cell.value - sheet = workbook[sheet_name] - sheet_updates = 0 - print(f"Checking formulas in sheet: {sheet_name}") - - # Special handling for Graphics sheet rows 25-27 - if sheet_name == "Graphics": - # We'll create similar formulas for columns H, O, U, AA, AG based on column C - # First, let's process column C for rows 25-27 as usual - for row_num in range(25, 28): # 25, 26, 27 - # Process column C first - cell_coord_c = f"C{row_num}" - try: - cell_c = sheet[cell_coord_c] - if cell_c.data_type == 'f' and cell_c.value and isinstance(cell_c.value, str): - print(f"Special handling for Graphics cell {cell_coord_c}: {cell_c.value}") - original_formula = cell_c.value - updated_formula = original_formula - - # Check if formula contains {store_name} or any of the old sheet names - needs_update = '{store_name}' in original_formula - if not needs_update: - for old_name in sheet_name_mapping.keys(): - if old_name in original_formula: - needs_update = True - break - - if needs_update: - for old_name, new_name in sheet_name_mapping.items(): - updated_formula = updated_formula.replace(old_name, new_name) - - if updated_formula != original_formula: - cell_c.value = updated_formula - sheet_updates += 1 - total_updates += 1 - print(f"Force updated formula in {sheet_name} cell {cell_coord_c}: {original_formula} -> {updated_formula}") - - # Now create similar formulas for H, O, U, AA, AG columns - # These should reference the specific year's forecast sheet - year_cols = { - 'H': '2025 – Forecast', - 'O': '2026 – Forecast', - 'U': '2027 – Forecast', - 'AA': '2028 – Forecast', - 'AG': '2029 – Forecast' - } - - for col, year_prefix in year_cols.items(): - cell_coord = f"{col}{row_num}" - try: - # Create a formula for this cell that references the specific year's forecast sheet - cell_ref = f"C{row_num - 15}" if row_num == 25 else f"C{row_num - 14}" # Map to row 10, 11, 12 - - # Check if the cell already exists - cell = sheet[cell_coord] - - # Create a new formula referencing the specific year's sheet - new_sheet_name = f"{year_prefix} {sheet_name_mapping.get('2025 – Forecast {store_name}', '').replace('2025 – Forecast ', '')}" - - # Determine the row reference based on the current row - if row_num == 25: - row_ref = "10" - elif row_num == 26: - row_ref = "11" - else: # row_num == 27 - row_ref = "12" - - new_formula = f"=SUM('{new_sheet_name}'!C{row_ref}:E{row_ref})" - - # Only update if needed - if cell.value != new_formula: - original_value = cell.value - cell.value = new_formula - sheet_updates += 1 - total_updates += 1 - print(f"Created/updated formula in {sheet_name} cell {cell_coord}: {original_value} -> {new_formula}") - except Exception as e: - print(f"Error processing cell {cell_coord}: {e}") - except Exception as e: - print(f"Error processing cell {cell_coord_c}: {e}") - - # Iterate through all cells in the sheet - for row in sheet.iter_rows(): - for cell in row: - # Skip rows 25-27 in Graphics sheet as they're handled separately - if sheet_name == "Graphics" and cell.row >= 25 and cell.row <= 27: - continue - - # Check if the cell contains a formula - if cell.data_type == 'f' and cell.value: - try: - # Get the formula as a string - formula = cell.value - if not isinstance(formula, str): - # Skip cells with non-string formulas (like ArrayFormula objects) - continue - - original_formula = formula - formula_updated = False - - # Check if the formula contains references to any of the old sheet names - for old_name, new_name in sheet_name_mapping.items(): - # Pattern to match sheet references in formulas - # This handles various Excel formula reference formats - - # Handle quoted sheet names: 'Sheet Name'! - pattern1 = f"'({re.escape(old_name)})'" - replacement1 = f"'{new_name}'" - new_formula = re.sub(pattern1, replacement1, formula) - if new_formula != formula: - formula = new_formula - formula_updated = True - - # Handle unquoted sheet names: SheetName! - pattern2 = f"([^']|^)({re.escape(old_name)})!" - replacement2 = f"\\1{new_name}!" - new_formula = re.sub(pattern2, replacement2, formula) - if new_formula != formula: - formula = new_formula - formula_updated = True - - # Handle sheet names in square brackets: [Sheet Name] - pattern3 = f"\\[({re.escape(old_name)})\\]" - replacement3 = f"[{new_name}]" - new_formula = re.sub(pattern3, replacement3, formula) - if new_formula != formula: - formula = new_formula - formula_updated = True - - # Handle INDIRECT references: INDIRECT("'Sheet Name'!A1") - pattern4 = f'INDIRECT\\("\'({re.escape(old_name)})\'!' - replacement4 = f'INDIRECT("\'({new_name})\'!' - new_formula = re.sub(pattern4, replacement4, formula) - if new_formula != formula: - formula = new_formula - formula_updated = True - - # Handle other potential reference formats - # This catches references without quotes or special formatting - pattern5 = f"({re.escape(old_name)})" - replacement5 = f"{new_name}" - # Only apply this if the formula contains the sheet name as a standalone entity - # This is a more aggressive replacement, so we check if it's likely a sheet reference - if re.search(f"\\b{re.escape(old_name)}\\b", formula) and "!" in formula: - new_formula = re.sub(pattern5, replacement5, formula) - if new_formula != formula: - formula = new_formula - formula_updated = True - - # We're handling rows 25-27 separately now, so this section is no longer needed - - # If the formula was changed, update the cell - if formula_updated: - try: - cell.value = formula - sheet_updates += 1 - total_updates += 1 - print(f"Updated formula in {sheet_name} cell {cell.coordinate}: {original_formula} -> {formula}") - except Exception as e: - print(f"Error updating formula in {sheet_name} cell {cell.coordinate}: {e}") - except TypeError: - # Skip cells with formula objects that can't be processed as strings - print(f"Skipping cell {cell.coordinate} in {sheet_name} with non-string formula type: {type(cell.value)}") - - print(f"Updated {sheet_updates} formulas in sheet {sheet_name}") - - except Exception as sheet_error: - print(f"Error processing sheet {sheet_name}: {sheet_error}") + # Check if the formula references any of the renamed sheets + references_renamed_sheet = False + for old_name in sheet_name_mapping.keys(): + if old_name in original_formula: + references_renamed_sheet = True + break + + if references_renamed_sheet: + formula_cells.append((cell, original_formula)) - # Update defined names in the workbook if any - if hasattr(workbook, 'defined_names') and workbook.defined_names: - print("Checking defined names in workbook...") - names_updated = 0 + print(f"Found {len(formula_cells)} cells with formulas referencing renamed sheets (out of {total_formulas} total formulas)") + + # Second pass: Update the formulas + for cell, original_formula in formula_cells: + updated_formula = original_formula + formula_updated = False - for name in workbook.defined_names: + # Check if the formula references any of the renamed sheets + for old_name, new_name in sheet_name_mapping.items(): + # Pattern 1: Sheet name in single quotes: 'Sheet Name'! + if f"'{old_name}'!" in updated_formula: + updated_formula = updated_formula.replace(f"'{old_name}'!", f"'{new_name}'!") + formula_updated = True + + # Pattern 2: Sheet name without quotes: SheetName! + # We need to be careful with this pattern to avoid partial matches + pattern = re.compile(f"(^|[^'])({re.escape(old_name)})!") + if pattern.search(updated_formula): + updated_formula = pattern.sub(f"\\1{new_name}!", updated_formula) + formula_updated = True + + # Pattern 3: Sheet name in INDIRECT function: INDIRECT("'Sheet Name'!...") + if f"INDIRECT(\"'{old_name}'!" in updated_formula: + updated_formula = updated_formula.replace( + f"INDIRECT(\"'{old_name}'!", + f"INDIRECT(\"'{new_name}'!" + ) + formula_updated = True + + # Pattern 4: Sheet name in double quotes: "Sheet Name"! + if f"\"{old_name}\"!" in updated_formula: + updated_formula = updated_formula.replace(f"\"{old_name}\"!", f"\"{new_name}\"!") + formula_updated = True + + # Pattern 5: Simple text replacement for any remaining instances + # Only do this if we're sure it's a sheet reference + if old_name in updated_formula and not formula_updated: + updated_formula = updated_formula.replace(old_name, new_name) + formula_updated = True + + # If the formula was updated, set it back to the cell + if formula_updated: try: - # Get the defined name value (formula) - destinations = workbook.defined_names[name].destinations - for sheet_title, coordinate in destinations: - if sheet_title in sheet_name_mapping: - # This defined name points to a renamed sheet - new_sheet_title = sheet_name_mapping[sheet_title] - # We need to recreate the defined name with the new sheet reference - # This is a simplification - in a real implementation you'd need to - # preserve all properties of the original defined name - names_updated += 1 - print(f"Updated defined name {name} to reference {new_sheet_title} instead of {sheet_title}") - except Exception as name_error: - print(f"Error updating defined name {name}: {name_error}") - - print(f"Updated {names_updated} defined names in workbook") + cell.value = updated_formula + updates_count += 1 + print(f"Updated formula in cell {cell.coordinate}: {original_formula} -> {updated_formula}") + except Exception as e: + print(f"Error updating formula in cell {cell.coordinate}: {e}") - print(f"Total formula updates across all sheets: {total_updates}") + # Special handling for specific cells that might have been missed + # These are known cells with complex formulas referencing the forecast sheets + special_cells = ['C4', 'H4', 'O4', 'U4', 'AA4', 'AG4', + 'C5', 'H5', 'O5', 'U5', 'AA5', 'AG5', + 'C6', 'H6', 'O6', 'U6', 'AA6', 'AG6', + 'C25', 'H25', 'O25', 'U25', 'AA25', 'AG25', + 'C26', 'H26', 'O26', 'U26', 'AA26', 'AG26', + 'C27', 'H27', 'O27', 'U27', 'AA27', 'AG27'] + # Print all cells in the Graphics sheet that have formulas + print("\nDiagnostic: Checking all cells with formulas in Graphics sheet:") + formula_cells_diagnostic = [] + for row in graphics_sheet.iter_rows(): + for cell in row: + if cell.data_type == 'f' and cell.value and isinstance(cell.value, str): + formula = cell.value + if any(old_name in formula for old_name in sheet_name_mapping.keys()): + formula_cells_diagnostic.append(f"{cell.coordinate}: {formula[:50]}...") + + print(f"Found {len(formula_cells_diagnostic)} cells with formulas referencing forecast sheets:") + for cell_info in formula_cells_diagnostic: + print(f" {cell_info}") + + for cell_coord in special_cells: + if cell_coord not in processed_cells: + try: + cell = graphics_sheet[cell_coord] + if cell.data_type == 'f' and cell.value and isinstance(cell.value, str): + original_formula = cell.value + updated_formula = original_formula + formula_updated = False + + for old_name, new_name in sheet_name_mapping.items(): + if old_name in updated_formula: + updated_formula = updated_formula.replace(old_name, new_name) + formula_updated = True + + if formula_updated: + cell.value = updated_formula + updates_count += 1 + print(f"Special handling: Updated formula in cell {cell_coord}: {original_formula} -> {updated_formula}") + except Exception as e: + print(f"Error in special handling for cell {cell_coord}: {e}") + + print(f"Updated {updates_count} formula references in Graphics sheet") + + # Check if there are any remaining formulas with the old sheet names + remaining_formulas = [] + for row in graphics_sheet.iter_rows(): + for cell in row: + if cell.data_type == 'f' and cell.value and isinstance(cell.value, str): + formula = cell.value + if any(old_name in formula for old_name in sheet_name_mapping.keys()): + remaining_formulas.append(f"{cell.coordinate}: {formula[:50]}...") + + if remaining_formulas: + print(f"Warning: Found {len(remaining_formulas)} formulas that still reference old sheet names:") + for formula in remaining_formulas: + print(f" {formula}") + else: + print("All formulas have been successfully updated!") + + return updates_count + except KeyError: + print("Graphics sheet not found in workbook") + return 0 except Exception as e: - print(f"Error updating formulas in workbook: {e}") + print(f"Error updating formula references: {e}") + return 0 if __name__ == "__main__": # For testing purposes