Implement Excel file generation with variable injection from config.json
This commit is contained in:
8
.gitignore
vendored
8
.gitignore
vendored
@@ -23,6 +23,10 @@ Thumbs.db
|
||||
logs
|
||||
*.log
|
||||
|
||||
# Generated files
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Output directory
|
||||
output/
|
||||
*.xlsx
|
||||
@@ -5,7 +5,7 @@
|
||||
"company_name": "Footprints AI",
|
||||
"email": "denisa@example.com",
|
||||
"phone": "+40 712 345 678",
|
||||
"store_name": "Media Romania",
|
||||
"store_name": "Profi Romania",
|
||||
"country": "Romania",
|
||||
"starting_date": "2025-02-01",
|
||||
"duration": 36,
|
||||
|
||||
132
create_excel.py
132
create_excel.py
@@ -6,7 +6,7 @@ import datetime
|
||||
import re
|
||||
from pathlib import Path
|
||||
from dateutil.relativedelta import relativedelta
|
||||
import openpyxl
|
||||
from update_excel import update_excel_variables
|
||||
|
||||
def create_excel_from_template():
|
||||
"""
|
||||
@@ -63,8 +63,14 @@ def create_excel_from_template():
|
||||
shutil.copy2(template_path, output_path)
|
||||
print(f"Excel file created successfully: {output_path}")
|
||||
|
||||
# Now inject variables from config.json into the Variables sheet
|
||||
inject_variables(output_path, config)
|
||||
# Update the Excel file with variables from config.json
|
||||
print("Updating Excel file with variables from config.json...")
|
||||
update_result = update_excel_variables(output_path)
|
||||
|
||||
if update_result:
|
||||
print("Excel file updated successfully with variables from config.json")
|
||||
else:
|
||||
print("Warning: Failed to update Excel file with variables from config.json")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
@@ -125,125 +131,5 @@ def calculate_years(starting_date, duration):
|
||||
print(f"Error calculating years: {e}")
|
||||
return default_years
|
||||
|
||||
def inject_variables(excel_path, config):
|
||||
"""
|
||||
Inject variables from config.json into the Variables sheet of the Excel file.
|
||||
|
||||
Args:
|
||||
excel_path (str): Path to the Excel file
|
||||
config (dict): Configuration data from config.json
|
||||
"""
|
||||
try:
|
||||
# Load the workbook
|
||||
workbook = openpyxl.load_workbook(excel_path)
|
||||
|
||||
# Try to find the Variables sheet
|
||||
sheet_names = workbook.sheetnames
|
||||
variables_sheet = None
|
||||
|
||||
# Print all sheet names for debugging
|
||||
print("Available sheets:", sheet_names)
|
||||
|
||||
# Look for the Variables sheet by name (case-insensitive)
|
||||
for sheet_name in sheet_names:
|
||||
if "variable" in sheet_name.lower():
|
||||
variables_sheet = workbook[sheet_name]
|
||||
print(f"Found Variables sheet: '{sheet_name}'")
|
||||
break
|
||||
|
||||
# If Variables sheet not found by name, try the last sheet
|
||||
if variables_sheet is None and sheet_names:
|
||||
last_sheet_name = sheet_names[-1]
|
||||
variables_sheet = workbook[last_sheet_name]
|
||||
print(f"Using last sheet '{last_sheet_name}' as Variables sheet")
|
||||
|
||||
# If still not found, try all sheets and look for specific cell patterns
|
||||
if variables_sheet is None:
|
||||
for sheet_name in sheet_names:
|
||||
sheet = workbook[sheet_name]
|
||||
# Check if this sheet has a cell B2 with a value
|
||||
if sheet["B2"].value is not None:
|
||||
variables_sheet = sheet
|
||||
print(f"Using sheet '{sheet_name}' as it has data in cell B2")
|
||||
break
|
||||
|
||||
if variables_sheet is None:
|
||||
print("Warning: Variables sheet not found. No variables were injected.")
|
||||
return
|
||||
|
||||
# Get user data from config
|
||||
user_data = config.get("user_data", {})
|
||||
|
||||
# Map cell references to config values based on the image
|
||||
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),
|
||||
|
||||
# Website, App, Loyalty
|
||||
"B43": user_data.get("website_visitors", 0),
|
||||
"B44": user_data.get("app_users", 0),
|
||||
"B45": user_data.get("loyalty_users", 0),
|
||||
|
||||
# Social Media
|
||||
"B49": user_data.get("facebook_followers", 0),
|
||||
"B50": user_data.get("instagram_followers", 0),
|
||||
"B51": user_data.get("google_views", 0)
|
||||
}
|
||||
|
||||
# Inject values into the Variables sheet
|
||||
print(f"Injecting variables into sheet: {variables_sheet.title}")
|
||||
for cell_ref, value in cell_mappings.items():
|
||||
try:
|
||||
# Check if cell exists
|
||||
if cell_ref in variables_sheet:
|
||||
variables_sheet[cell_ref] = value
|
||||
print(f"Set {cell_ref} = {value}")
|
||||
else:
|
||||
print(f"Warning: Cell {cell_ref} not found in sheet")
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not set value for cell {cell_ref}: {e}")
|
||||
|
||||
# Save the workbook
|
||||
workbook.save(excel_path)
|
||||
print(f"Variables successfully injected into {excel_path}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error injecting variables: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
create_excel_from_template()
|
||||
|
||||
@@ -1,381 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import datetime
|
||||
import re
|
||||
import traceback
|
||||
from pathlib import Path
|
||||
from dateutil.relativedelta import relativedelta
|
||||
import sys
|
||||
import unicodedata
|
||||
from openpyxl import load_workbook
|
||||
import zipfile
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
def create_excel_from_template():
|
||||
"""
|
||||
Create a copy of the Excel template, replacing {store_name} with the value from config.json
|
||||
and save it to the output folder.
|
||||
"""
|
||||
# Define paths
|
||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
config_path = os.path.join(script_dir, 'config.json')
|
||||
template_path = os.path.join(script_dir, 'template', 'Footprints AI for {store_name} - Retail Media Business Case Calculations.xlsx')
|
||||
output_dir = os.path.join(script_dir, 'output')
|
||||
|
||||
print(f"[DEBUG] script_dir={script_dir}")
|
||||
print(f"[DEBUG] config_path={config_path}")
|
||||
print(f"[DEBUG] template_path={template_path}")
|
||||
print(f"[DEBUG] output_dir={output_dir}")
|
||||
print(f"[DEBUG] cwd={os.getcwd()}")
|
||||
|
||||
# Ensure output directory exists
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
if not os.path.exists(config_path):
|
||||
print(f"[ERROR] config.json not found at: {config_path}")
|
||||
return False
|
||||
|
||||
# Read config.json to get store_name, starting_date, and duration
|
||||
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', '')
|
||||
starting_date = user_data.get('starting_date', '')
|
||||
duration = user_data.get('duration', 36)
|
||||
|
||||
# If store_name is empty, use a default value
|
||||
if not store_name:
|
||||
store_name = "Your Store"
|
||||
|
||||
# Calculate years array based on starting_date and duration
|
||||
years = calculate_years(starting_date, duration)
|
||||
print(f"Years in the period: {years}")
|
||||
except Exception as e:
|
||||
print(f"Error reading config file: {e}")
|
||||
print(traceback.format_exc())
|
||||
return False
|
||||
|
||||
# Use first and last years from the array in the 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:
|
||||
# Fallback to current year if years array is empty
|
||||
current_year = datetime.datetime.now().year
|
||||
year_range = f"{current_year}"
|
||||
|
||||
# Create output filename with store_name and year range
|
||||
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)
|
||||
|
||||
print(f"[DEBUG] output_path={output_path}")
|
||||
|
||||
if not os.path.exists(template_path):
|
||||
print(f"[ERROR] Template not found at: {template_path}")
|
||||
return False
|
||||
|
||||
# Copy the template to the output directory with the new name
|
||||
try:
|
||||
shutil.copy2(template_path, output_path)
|
||||
if not os.path.exists(output_path):
|
||||
print(f"[ERROR] Copy reported success but file missing: {output_path}")
|
||||
return False
|
||||
print(f"Excel file created successfully: {output_path}")
|
||||
|
||||
# Rename any sheets that contain the {store_name} token
|
||||
try:
|
||||
renamed_count = rename_store_placeholders(output_path, store_name)
|
||||
print(f"[RENAME] Sheets renamed: {renamed_count}")
|
||||
except Exception as e:
|
||||
print(f"[RENAME] Unexpected error while renaming sheets: {e}")
|
||||
|
||||
# Now inject variables from config.json into the Variables sheet
|
||||
ok = inject_variables(output_path, config)
|
||||
if not ok:
|
||||
print("[ERROR] inject_variables failed.")
|
||||
return False
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error creating Excel file: {e}")
|
||||
print(traceback.format_exc())
|
||||
return False
|
||||
|
||||
def calculate_years(starting_date, duration):
|
||||
"""
|
||||
Calculate an array of years that appear in the period from starting_date for duration months.
|
||||
|
||||
Args:
|
||||
starting_date (str): Date in format dd/mm/yyyy, dd.mm.yyyy, or yyyy-mm-dd
|
||||
duration (int): Number of months, including the starting month
|
||||
|
||||
Returns:
|
||||
list: Array of years in the period [year1, year2, ...]
|
||||
"""
|
||||
# Default result if we can't parse the date
|
||||
default_years = [datetime.datetime.now().year]
|
||||
|
||||
# If starting_date is empty, return current year
|
||||
if not starting_date:
|
||||
return default_years
|
||||
|
||||
try:
|
||||
# Try to parse the date, supporting multiple formats
|
||||
if '/' in starting_date:
|
||||
day, month, year = map(int, starting_date.split('/'))
|
||||
elif '.' in starting_date:
|
||||
day, month, year = map(int, starting_date.split('.'))
|
||||
elif '-' in starting_date:
|
||||
# Handle yyyy-mm-dd format (from HTML date input)
|
||||
parts = starting_date.split('-')
|
||||
if len(parts) == 3:
|
||||
year, month, day = map(int, parts)
|
||||
else:
|
||||
return default_years
|
||||
else:
|
||||
# If format is not recognized, return default
|
||||
return default_years
|
||||
|
||||
# Create datetime object for starting date
|
||||
start_date = datetime.datetime(year, month, day)
|
||||
|
||||
# Calculate end date (starting date + duration months - 1 day)
|
||||
end_date = start_date + relativedelta(months=duration-1)
|
||||
|
||||
# Create a set of years (to avoid duplicates)
|
||||
years_set = set()
|
||||
|
||||
# Add starting year
|
||||
years_set.add(start_date.year)
|
||||
|
||||
# Add ending year
|
||||
years_set.add(end_date.year)
|
||||
|
||||
# If there are years in between, add those too
|
||||
for y in range(start_date.year + 1, end_date.year):
|
||||
years_set.add(y)
|
||||
|
||||
# Convert set to sorted list
|
||||
return sorted(list(years_set))
|
||||
|
||||
except Exception as e:
|
||||
print(f"Error calculating years: {e}")
|
||||
return default_years
|
||||
|
||||
def _normalize_name(s: str) -> str:
|
||||
"""Normalize sheet names to avoid issues with en-dash/nbsp/casing."""
|
||||
if s is None:
|
||||
return ""
|
||||
s = unicodedata.normalize("NFKC", s)
|
||||
return s.replace("\u2013", "-").replace("\u00A0", " ").strip().lower()
|
||||
|
||||
def _diagnose_xlsx(path: str):
|
||||
"""Inspect the XLSX container to list sheets and their types when openpyxl sees none."""
|
||||
try:
|
||||
with zipfile.ZipFile(path, 'r') as z:
|
||||
print("[DIAG] ZIP entries:", len(z.namelist()))
|
||||
# Workbook relationships and workbook xml
|
||||
if 'xl/workbook.xml' in z.namelist():
|
||||
xml = z.read('xl/workbook.xml')
|
||||
root = ET.fromstring(xml)
|
||||
ns = {'ns': 'http://schemas.openxmlformats.org/spreadsheetml/2006/main'}
|
||||
sheets = root.findall('.//ns:sheets/ns:sheet', ns)
|
||||
if not sheets:
|
||||
print("[DIAG] No <sheet> nodes found in xl/workbook.xml")
|
||||
for s in sheets:
|
||||
print(f"[DIAG] sheet name={s.get('name')!r} id={s.get('sheetId')} r:id={s.get('{http://schemas.openxmlformats.org/officeDocument/2006/relationships}id')}")
|
||||
# Check for 'fileVersion' and workbookPr flags that sometimes confuse parsers
|
||||
wbpr = root.find('.//ns:workbookPr', ns)
|
||||
if wbpr is not None:
|
||||
print("[DIAG] workbookPr attrs:", wbpr.attrib)
|
||||
else:
|
||||
print("[DIAG] Missing xl/workbook.xml (file may be corrupted or not an xlsx).")
|
||||
|
||||
# Look for worksheet vs chartsheet parts
|
||||
worksheet_parts = [n for n in z.namelist() if n.startswith('xl/worksheets/sheet') and n.endswith('.xml')]
|
||||
chartsheet_parts = [n for n in z.namelist() if n.startswith('xl/chartsheets/sheet') and n.endswith('.xml')]
|
||||
dialogsheets = [n for n in z.namelist() if n.startswith('xl/dialogsheets/') and n.endswith('.xml')]
|
||||
print(f"[DIAG] worksheets={len(worksheet_parts)}, chartsheets={len(chartsheet_parts)}, dialogsheets={len(dialogsheets)}")
|
||||
if chartsheet_parts and not worksheet_parts:
|
||||
print("[DIAG] This workbook appears to contain only chart sheets (no worksheets). openpyxl will show zero sheetnames.")
|
||||
except Exception as e:
|
||||
print(f"[DIAG] Failed to inspect xlsx: {e}")
|
||||
print(traceback.format_exc())
|
||||
|
||||
def _sanitize_sheet_title(title: str) -> str:
|
||||
"""
|
||||
Make a worksheet title Excel-safe:
|
||||
- Replace invalid characters : \ / ? * [ ]
|
||||
- Trim to 31 chars
|
||||
"""
|
||||
invalid = r'[:\\/\?\*\[\]]'
|
||||
safe = re.sub(invalid, ' ', title).strip()
|
||||
if len(safe) > 31:
|
||||
safe = safe[:31]
|
||||
return safe
|
||||
|
||||
def rename_store_placeholders(excel_path: str, store_name: str) -> int:
|
||||
"""
|
||||
Rename any worksheet whose title contains '{store_name}' by replacing the token
|
||||
with the provided store_name, enforcing Excel naming rules and uniqueness.
|
||||
Returns the number of sheets renamed.
|
||||
"""
|
||||
try:
|
||||
wb = load_workbook(excel_path, data_only=False)
|
||||
except Exception as e:
|
||||
print(f"[RENAME] Could not open workbook for renaming: {e}")
|
||||
return 0
|
||||
|
||||
renamed = 0
|
||||
existing = set(ws.title for ws in wb.worksheets)
|
||||
|
||||
for ws in wb.worksheets:
|
||||
old = ws.title
|
||||
if "{store_name}" not in old:
|
||||
continue
|
||||
new_title_raw = old.replace("{store_name}", store_name or "Your Store")
|
||||
new_title = _sanitize_sheet_title(new_title_raw)
|
||||
|
||||
# Ensure uniqueness by appending (2), (3), ...
|
||||
candidate = new_title
|
||||
suffix = 2
|
||||
while candidate in existing and candidate != old:
|
||||
base = new_title
|
||||
# leave room for " (nn)"
|
||||
max_base = 31 - (len(str(suffix)) + 3)
|
||||
if len(base) > max_base:
|
||||
base = base[:max_base]
|
||||
candidate = f"{base} ({suffix})"
|
||||
suffix += 1
|
||||
|
||||
if candidate != old:
|
||||
try:
|
||||
ws.title = candidate
|
||||
existing.discard(old)
|
||||
existing.add(candidate)
|
||||
renamed += 1
|
||||
print(f"[RENAME] '{old}' → '{candidate}'")
|
||||
except Exception as e:
|
||||
print(f"[RENAME] Failed to rename '{old}' to '{candidate}': {e}")
|
||||
|
||||
if renamed > 0:
|
||||
try:
|
||||
wb.save(excel_path)
|
||||
print(f"[RENAME] Saved workbook after renaming {renamed} sheet(s).")
|
||||
except Exception as e:
|
||||
print(f"[RENAME] Failed to save workbook after renames: {e}")
|
||||
else:
|
||||
print("[RENAME] No sheets contained '{store_name}'.")
|
||||
|
||||
return renamed
|
||||
|
||||
def inject_variables(excel_path, config):
|
||||
"""
|
||||
Inject variables from config.json into the Variables sheet of the Excel file.
|
||||
Linux-only path: uses openpyxl (no Excel required). This reads/writes .xlsx safely; .xlsm VBA projects are not preserved if you re-save them.
|
||||
"""
|
||||
user_data = config.get("user_data", {})
|
||||
|
||||
# Map cell references to config values based on the image
|
||||
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),
|
||||
"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),
|
||||
"H38": user_data.get("supermarket_store_type", {}).get("stores_number", 0),
|
||||
"C38": user_data.get("supermarket_store_type", {}).get("monthly_transactions", 0),
|
||||
"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),
|
||||
"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),
|
||||
"H39": user_data.get("hypermarket_store_type", {}).get("stores_number", 0),
|
||||
"C39": user_data.get("hypermarket_store_type", {}).get("monthly_transactions", 0),
|
||||
"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),
|
||||
"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),
|
||||
"B43": user_data.get("website_visitors", 0),
|
||||
"B44": user_data.get("app_users", 0),
|
||||
"B45": user_data.get("loyalty_users", 0),
|
||||
"B49": user_data.get("facebook_followers", 0),
|
||||
"B50": user_data.get("instagram_followers", 0),
|
||||
"B51": user_data.get("google_views", 0),
|
||||
"B53": user_data.get("sms_users", 0)
|
||||
}
|
||||
|
||||
# Warn if trying to process a macro-enabled workbook: openpyxl will not preserve VBA
|
||||
if excel_path.lower().endswith(".xlsm"):
|
||||
print("Warning: .xlsm detected. openpyxl cannot preserve VBA projects; consider switching to a .xlsx template or running this step on Windows/Excel.")
|
||||
|
||||
# ---- openpyxl fallback (works on Linux, no Excel required) ----
|
||||
try:
|
||||
wb = load_workbook(excel_path, data_only=False)
|
||||
if not wb.sheetnames:
|
||||
print("[WARN] openpyxl reports no worksheets. Running container diagnostics…")
|
||||
_diagnose_xlsx(excel_path)
|
||||
print("Available sheets (openpyxl):", [repr(s) for s in wb.sheetnames])
|
||||
|
||||
# Find Variables sheet (case-insensitive, normalized)
|
||||
target_idx = None
|
||||
for idx, name in enumerate(wb.sheetnames):
|
||||
if "variable" in _normalize_name(name):
|
||||
target_idx = idx
|
||||
break
|
||||
if target_idx is None:
|
||||
target_idx = len(wb.sheetnames) - 1 if wb.sheetnames else None
|
||||
if target_idx is not None:
|
||||
print(f"Variables sheet not found by name; using last sheet: {wb.sheetnames[target_idx]}")
|
||||
else:
|
||||
print("Suggestion: Ensure the template has at least one normal worksheet (not only chartsheets). Open and 'Save As' a regular .xlsx in Excel.")
|
||||
|
||||
if target_idx is None:
|
||||
print("Warning: Workbook has no sheets. No variables were injected.")
|
||||
return False
|
||||
|
||||
ws = wb[wb.sheetnames[target_idx]]
|
||||
|
||||
# Write values
|
||||
for cell_ref, value in cell_mappings.items():
|
||||
try:
|
||||
ws[cell_ref].value = value
|
||||
print(f"[openpyxl] Set {cell_ref} = {value}")
|
||||
except Exception as e:
|
||||
print(f"Warning: Could not set value for cell {cell_ref}: {e}")
|
||||
|
||||
# Ensure we're saving to .xlsx path to avoid accidental macro loss if template was .xlsm
|
||||
save_path = excel_path
|
||||
if save_path.lower().endswith(".xlsm"):
|
||||
save_path = save_path[:-5] + ".xlsx"
|
||||
print(f"Saving as {save_path} to avoid stripping VBA from .xlsm.")
|
||||
|
||||
wb.save(save_path)
|
||||
print(f"Variables successfully injected into {save_path} using openpyxl")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"Error in openpyxl fallback: {e}")
|
||||
print(traceback.format_exc())
|
||||
return False
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
ok = create_excel_from_template()
|
||||
sys.exit(0 if ok else 1)
|
||||
except Exception as e:
|
||||
print(f"[FATAL] Unhandled exception: {e}")
|
||||
print(traceback.format_exc())
|
||||
sys.exit(2)
|
||||
@@ -35,8 +35,8 @@ app.post('/calculate', async (req, res) => {
|
||||
await updateConfig(formData);
|
||||
console.log('Config file updated successfully');
|
||||
|
||||
// Run Python script to create Excel file with variables injection
|
||||
exec('python3 create_excel_xlwings.py', (error, stdout, stderr) => {
|
||||
// Run Python script to create Excel file
|
||||
exec('python3 create_excel.py', (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error(`Error executing Python script: ${error}`);
|
||||
console.error(`stderr: ${stderr}`);
|
||||
|
||||
Binary file not shown.
123
update_excel.py
Executable file
123
update_excel.py
Executable file
@@ -0,0 +1,123 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import os
|
||||
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}")
|
||||
|
||||
# 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
|
||||
|
||||
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")
|
||||
Reference in New Issue
Block a user