Files
bussines_case_automation/update_excel.py

213 lines
10 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
import json
import os
import re
import openpyxl
from openpyxl.utils import get_column_letter
def update_excel_variables(excel_path):
"""
Update the Variables sheet in the Excel file with values from config.json
Args:
excel_path (str): Path to the Excel file to update
Returns:
bool: True if successful, False otherwise
"""
# Define paths
script_dir = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(script_dir, 'config.json')
try:
# Load config.json
with open(config_path, 'r') as f:
config = json.load(f)
user_data = config.get('user_data', {})
# Load Excel workbook
print(f"Opening Excel file: {excel_path}")
wb = openpyxl.load_workbook(excel_path)
# Try to access the Variables sheet
try:
# First try by name
sheet = wb['Variables']
except KeyError:
# If not found by name, try to access the last sheet
sheet_names = wb.sheetnames
if sheet_names:
print(f"Variables sheet not found by name. Using last sheet: {sheet_names[-1]}")
sheet = wb[sheet_names[-1]]
else:
print("No sheets found in the workbook")
return False
# Map config variables to Excel cells based on the provided mapping
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),
# Convert boolean to 1/0 for has_digital_screens
'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),
# Convert boolean to 1/0 for has_in_store_radio
'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),
# Supermarket store type
'H38': user_data.get('supermarket_store_type', {}).get('stores_number', 0),
'C38': user_data.get('supermarket_store_type', {}).get('monthly_transactions', 0),
# Convert boolean to 1/0 for has_digital_screens
'I38': 1 if user_data.get('supermarket_store_type', {}).get('has_digital_screens', False) else 0,
'J38': user_data.get('supermarket_store_type', {}).get('screen_count', 0),
'K38': user_data.get('supermarket_store_type', {}).get('screen_percentage', 0),
# Convert boolean to 1/0 for has_in_store_radio
'M38': 1 if user_data.get('supermarket_store_type', {}).get('has_in_store_radio', False) else 0,
'N38': user_data.get('supermarket_store_type', {}).get('radio_percentage', 0),
# Hypermarket store type
'H39': user_data.get('hypermarket_store_type', {}).get('stores_number', 0),
'C39': user_data.get('hypermarket_store_type', {}).get('monthly_transactions', 0),
# Convert boolean to 1/0 for has_digital_screens
'I39': 1 if user_data.get('hypermarket_store_type', {}).get('has_digital_screens', False) else 0,
'J39': user_data.get('hypermarket_store_type', {}).get('screen_count', 0),
'K39': user_data.get('hypermarket_store_type', {}).get('screen_percentage', 0),
# Convert boolean to 1/0 for has_in_store_radio
'M39': 1 if user_data.get('hypermarket_store_type', {}).get('has_in_store_radio', False) else 0,
'N39': 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
print(f"Updated {cell_ref} with value: {value}")
except Exception as e:
print(f"Error updating cell {cell_ref}: {e}")
# Update sheet names - replace {store_name} with actual store name
store_name = user_data.get('store_name', '')
if store_name:
# Dictionary to store old sheet name to new sheet name mappings
sheet_name_mapping = {}
# Make a copy of the sheet names to avoid modifying during iteration
sheet_names = wb.sheetnames.copy()
for sheet_name in sheet_names:
if '{store_name}' in sheet_name:
new_sheet_name = sheet_name.replace('{store_name}', store_name)
# Get the sheet by its old name
sheet = wb[sheet_name]
# Set the new title
sheet.title = new_sheet_name
# Store the mapping
sheet_name_mapping[sheet_name] = new_sheet_name
print(f"Renamed sheet '{sheet_name}' to '{new_sheet_name}'")
# Update formulas in the Graphics sheet to reference the new sheet names
if sheet_name_mapping and 'Graphics' in wb.sheetnames:
print("Updating formulas in Graphics sheet...")
update_formulas_in_graphics_sheet(wb, sheet_name_mapping)
# Save the workbook
wb.save(excel_path)
print(f"Excel file updated successfully: {excel_path}")
return True
except Exception as e:
print(f"Error updating Excel file: {e}")
return False
def update_formulas_in_graphics_sheet(workbook, sheet_name_mapping):
"""
Update formulas in the Graphics sheet to reference the new sheet names
Args:
workbook: The openpyxl workbook object
sheet_name_mapping: Dictionary mapping old sheet names to new sheet names
"""
try:
graphics_sheet = workbook['Graphics']
print("Found Graphics sheet, updating formulas...")
# 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:
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
# 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 both quoted and unquoted sheet names
# Example: '2025 Forecast {store_name}'!$B$10 or [2025 Forecast {store_name}]!$B$10
# Handle quoted sheet names: 'Sheet Name'!
pattern1 = f"'({re.escape(old_name)})'"
replacement1 = f"'{new_name}'"
formula = re.sub(pattern1, replacement1, formula)
# Handle unquoted sheet names: SheetName!
pattern2 = f"([^']|^)({re.escape(old_name)})!"
replacement2 = f"\\1{new_name}!"
formula = re.sub(pattern2, replacement2, formula)
# Handle sheet names in square brackets: [Sheet Name]
pattern3 = f"\\[({re.escape(old_name)})\\]"
replacement3 = f"[{new_name}]"
formula = re.sub(pattern3, replacement3, formula)
# If the formula was changed, update the cell
if formula != original_formula:
try:
cell.value = formula
print(f"Updated formula in cell {cell.coordinate}: {original_formula} -> {formula}")
except Exception as e:
print(f"Error updating formula in cell {cell.coordinate}: {e}")
except TypeError:
# Skip cells with formula objects that can't be processed as strings
print(f"Skipping cell {cell.coordinate} with non-string formula type: {type(cell.value)}")
print("Finished updating formulas in Graphics sheet")
except KeyError:
print("Graphics sheet not found, skipping formula updates")
except Exception as e:
print(f"Error updating formulas in Graphics sheet: {e}")
if __name__ == "__main__":
# For testing purposes
import sys
if len(sys.argv) > 1:
excel_path = sys.argv[1]
update_excel_variables(excel_path)
else:
print("Please provide the path to the Excel file as an argument")