#!/usr/bin/env python3 """ Improved Excel creation script that processes templates in memory to prevent external link issues in Excel. """ import json import os import datetime from pathlib import Path from dateutil.relativedelta import relativedelta import openpyxl from openpyxl.utils import get_column_letter def create_excel_from_template(): """ Create an Excel file from template with all placeholders replaced in memory before saving to prevent external link issues. """ # Define paths script_dir = os.path.dirname(os.path.abspath(__file__)) config_path = os.path.join(script_dir, 'config.json') # Check for both possible template names template_dir = os.path.join(script_dir, 'template') # Try to find the template with either naming convention possible_templates = [ 'Footprints AI for {store_name} - Retail Media Business Case Calculations.xlsx', 'Footprints AI for store_name - Retail Media Business Case Calculations.xlsx' ] template_path = None for template_name in possible_templates: full_path = os.path.join(template_dir, template_name) if os.path.exists(full_path): template_path = full_path print(f"Found template: {template_name}") break if not template_path: print(f"Error: No template found in {template_dir}") return False output_dir = os.path.join(script_dir, 'output') # Ensure output directory exists os.makedirs(output_dir, exist_ok=True) # Read config.json try: with open(config_path, 'r') as f: config = json.load(f) user_data = config.get('user_data', {}) store_name = user_data.get('store_name', 'Your Store') starting_date = user_data.get('starting_date', '') duration = user_data.get('duration', 36) if not store_name: store_name = "Your Store" print(f"Processing for store: {store_name}") # Calculate years array years = calculate_years(starting_date, duration) calculated_years = years # For sheet visibility later print(f"Years in the period: {years}") except Exception as e: print(f"Error reading config file: {e}") return False # Determine year range for filename year_range = "" if years and len(years) > 0: if len(years) == 1: year_range = f"{years[0]}" else: year_range = f"{years[0]}-{years[-1]}" else: year_range = f"{datetime.datetime.now().year}" # Create output filename output_filename = f"Footprints AI for {store_name} - Retail Media Business Case Calculations {year_range}.xlsx" output_path = os.path.join(output_dir, output_filename) try: # STAGE 1: Load template and replace all placeholders in memory print("Loading template in memory...") wb = openpyxl.load_workbook(template_path, data_only=False) # Build mapping of placeholder patterns to actual values # Support both {store_name} and store_name formats placeholder_patterns = [ ('{store_name}', store_name), ('store_name', store_name) # New format without curly braces ] # STAGE 2: Replace placeholders in sheet names first print("Replacing placeholders in sheet names...") sheet_name_mappings = {} for sheet in wb.worksheets: old_title = sheet.title new_title = old_title # Replace all placeholder patterns in sheet name for placeholder, replacement in placeholder_patterns: if placeholder in new_title: new_title = new_title.replace(placeholder, replacement) print(f" Sheet name: '{old_title}' -> '{new_title}'") if old_title != new_title: # Store the mapping for formula updates sheet_name_mappings[old_title] = new_title # Also store with quotes for formula references sheet_name_mappings[f"'{old_title}'"] = f"'{new_title}'" # STAGE 3: Update all formulas and cell values BEFORE renaming sheets print("Updating formulas and cell values...") total_replacements = 0 for sheet in wb.worksheets: sheet_name = sheet.title replacements_in_sheet = 0 # Skip Variables sheet to avoid issues if 'Variables' in sheet_name: continue for row in sheet.iter_rows(): for cell in row: # Handle formulas if cell.data_type == 'f' and cell.value: original_formula = str(cell.value) new_formula = original_formula # First replace sheet references for old_ref, new_ref in sheet_name_mappings.items(): if old_ref in new_formula: new_formula = new_formula.replace(old_ref, new_ref) # Then replace any remaining placeholders for placeholder, replacement in placeholder_patterns: if placeholder in new_formula: new_formula = new_formula.replace(placeholder, replacement) if new_formula != original_formula: cell.value = new_formula replacements_in_sheet += 1 # Handle text values elif cell.value and isinstance(cell.value, str): original_value = str(cell.value) new_value = original_value for placeholder, replacement in placeholder_patterns: if placeholder in new_value: new_value = new_value.replace(placeholder, replacement) if new_value != original_value: cell.value = new_value replacements_in_sheet += 1 if replacements_in_sheet > 0: print(f" {sheet_name}: {replacements_in_sheet} replacements") total_replacements += replacements_in_sheet print(f"Total replacements: {total_replacements}") # STAGE 4: Now rename the sheets (after formulas are updated) print("Renaming sheets...") for sheet in wb.worksheets: old_title = sheet.title new_title = old_title for placeholder, replacement in placeholder_patterns: if placeholder in new_title: new_title = new_title.replace(placeholder, replacement) if old_title != new_title: sheet.title = new_title print(f" Renamed: '{old_title}' -> '{new_title}'") # Check if this is a forecast sheet and hide if needed if "Forecast" in new_title: try: # Extract year from sheet name sheet_year = int(new_title.split()[0]) if sheet_year not in calculated_years: sheet.sheet_state = 'hidden' print(f" Hidden sheet '{new_title}' (year {sheet_year} not in range)") except (ValueError, IndexError): pass # STAGE 5: Update Variables sheet with config values print("Updating Variables sheet...") if 'Variables' in wb.sheetnames: update_variables_sheet(wb['Variables'], user_data) # STAGE 6: Save the fully processed workbook print(f"Saving to: {output_path}") wb.save(output_path) print(f"✓ Excel file created successfully: {output_filename}") return True except Exception as e: print(f"Error creating Excel file: {e}") import traceback traceback.print_exc() return False def update_variables_sheet(sheet, user_data): """ Update the Variables sheet with values from config.json """ # Map config variables to Excel cells cell_mappings = { 'B2': user_data.get('store_name', ''), 'B31': user_data.get('starting_date', ''), 'B32': user_data.get('duration', 36), 'B37': user_data.get('open_days_per_month', 0), # Convenience store type 'H37': user_data.get('convenience_store_type', {}).get('stores_number', 0), 'C37': user_data.get('convenience_store_type', {}).get('monthly_transactions', 0), 'I37': 1 if user_data.get('convenience_store_type', {}).get('has_digital_screens', False) else 0, 'J37': user_data.get('convenience_store_type', {}).get('screen_count', 0), 'K37': user_data.get('convenience_store_type', {}).get('screen_percentage', 0), 'M37': 1 if user_data.get('convenience_store_type', {}).get('has_in_store_radio', False) else 0, 'N37': user_data.get('convenience_store_type', {}).get('radio_percentage', 0), # Minimarket store type 'H38': user_data.get('minimarket_store_type', {}).get('stores_number', 0), 'C38': user_data.get('minimarket_store_type', {}).get('monthly_transactions', 0), 'I38': 1 if user_data.get('minimarket_store_type', {}).get('has_digital_screens', False) else 0, 'J38': user_data.get('minimarket_store_type', {}).get('screen_count', 0), 'K38': user_data.get('minimarket_store_type', {}).get('screen_percentage', 0), 'M38': 1 if user_data.get('minimarket_store_type', {}).get('has_in_store_radio', False) else 0, 'N38': user_data.get('minimarket_store_type', {}).get('radio_percentage', 0), # Supermarket store type 'H39': user_data.get('supermarket_store_type', {}).get('stores_number', 0), 'C39': user_data.get('supermarket_store_type', {}).get('monthly_transactions', 0), 'I39': 1 if user_data.get('supermarket_store_type', {}).get('has_digital_screens', False) else 0, 'J39': user_data.get('supermarket_store_type', {}).get('screen_count', 0), 'K39': user_data.get('supermarket_store_type', {}).get('screen_percentage', 0), 'M39': 1 if user_data.get('supermarket_store_type', {}).get('has_in_store_radio', False) else 0, 'N39': user_data.get('supermarket_store_type', {}).get('radio_percentage', 0), # Hypermarket store type 'H40': user_data.get('hypermarket_store_type', {}).get('stores_number', 0), 'C40': user_data.get('hypermarket_store_type', {}).get('monthly_transactions', 0), 'I40': 1 if user_data.get('hypermarket_store_type', {}).get('has_digital_screens', False) else 0, 'J40': user_data.get('hypermarket_store_type', {}).get('screen_count', 0), 'K40': user_data.get('hypermarket_store_type', {}).get('screen_percentage', 0), 'M40': 1 if user_data.get('hypermarket_store_type', {}).get('has_in_store_radio', False) else 0, 'N40': user_data.get('hypermarket_store_type', {}).get('radio_percentage', 0), # On-site channels 'B43': user_data.get('website_visitors', 0), 'B44': user_data.get('app_users', 0), 'B45': user_data.get('loyalty_users', 0), # Off-site channels 'B49': user_data.get('facebook_followers', 0), 'B50': user_data.get('instagram_followers', 0), 'B51': user_data.get('google_views', 0), 'B52': user_data.get('email_subscribers', 0), 'B53': user_data.get('sms_users', 0), 'B54': user_data.get('whatsapp_contacts', 0) } # Update the cells for cell_ref, value in cell_mappings.items(): try: sheet[cell_ref].value = value print(f" Updated {cell_ref} = {value}") except Exception as e: print(f" Warning: Could not update {cell_ref}: {e}") def calculate_years(starting_date, duration): """ Calculate an array of years that appear in the period. """ default_years = [datetime.datetime.now().year] if not starting_date: return default_years try: # Parse date - support multiple formats if '/' in str(starting_date): day, month, year = map(int, str(starting_date).split('/')) elif '.' in str(starting_date): day, month, year = map(int, str(starting_date).split('.')) elif '-' in str(starting_date): # ISO format (yyyy-mm-dd) date_parts = str(starting_date).split('-') if len(date_parts) == 3: year, month, day = map(int, date_parts) else: return default_years else: return default_years # Create datetime object start_date = datetime.datetime(year, month, day) # Calculate end date end_date = start_date + relativedelta(months=duration-1) # Create set of years years_set = set() years_set.add(start_date.year) years_set.add(end_date.year) # Add any years in between for y in range(start_date.year + 1, end_date.year): years_set.add(y) return sorted(list(years_set)) except Exception as e: print(f"Error calculating years: {e}") return default_years if __name__ == "__main__": create_excel_from_template()