Initial commit: sales analysis template

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jonathan Pressnell
2026-02-06 09:16:34 -05:00
commit cf0b596449
38 changed files with 8001 additions and 0 deletions

240
setup_wizard.py Normal file
View File

@@ -0,0 +1,240 @@
"""
Interactive setup wizard for configuring the sales analysis template
Asks clarifying questions to configure config.py for your specific company and data
"""
import os
import sys
from pathlib import Path
def print_header(text):
"""Print a formatted header"""
print("\n" + "="*70)
print(f" {text}")
print("="*70 + "\n")
def ask_question(prompt, default=None, validator=None):
"""
Ask a question and return the answer
Args:
prompt: Question to ask
default: Default value if user just presses Enter
validator: Optional function to validate input
Returns:
User's answer (or default)
"""
if default:
full_prompt = f"{prompt} [{default}]: "
else:
full_prompt = f"{prompt}: "
while True:
answer = input(full_prompt).strip()
if not answer and default:
return default
elif not answer:
print(" Please provide an answer.")
continue
if validator:
try:
return validator(answer)
except Exception as e:
print(f" Invalid input: {e}")
continue
return answer
def validate_yes_no(answer):
"""Validate yes/no answer"""
answer_lower = answer.lower()
if answer_lower in ['y', 'yes', 'true', '1']:
return True
elif answer_lower in ['n', 'no', 'false', '0']:
return False
else:
raise ValueError("Please answer 'yes' or 'no'")
def validate_int(answer):
"""Validate integer answer"""
return int(answer)
def validate_file_exists(answer):
"""Validate that file exists"""
if not Path(answer).exists():
raise ValueError(f"File not found: {answer}")
return answer
def main():
"""Run the setup wizard"""
print_header("Sales Analysis Template - Setup Wizard")
print("This wizard will help you configure the template for your company's data.")
print("You can press Enter to accept defaults (shown in brackets).\n")
responses = {}
# Company Information
print_header("Company Information")
responses['company_name'] = ask_question("Company Name", default="Your Company Name")
responses['analysis_date'] = ask_question("Analysis Date (YYYY-MM-DD)", default="2026-01-12")
# Data File
print_header("Data File Configuration")
print("Where is your sales data CSV file located?")
data_file = ask_question("Data file name (e.g., sales_data.csv)", default="sales_data.csv")
# Check if file exists
if Path(data_file).exists():
print(f" ✓ Found: {data_file}")
else:
print(f" ⚠ Warning: {data_file} not found. Make sure to place it in the template directory.")
responses['data_file'] = data_file
# Column Mapping
print_header("Column Mapping")
print("What are the column names in your CSV file?")
print("(Press Enter to accept defaults if your columns match common names)\n")
responses['revenue_column'] = ask_question("Revenue/Amount column name", default="USD")
responses['date_column'] = ask_question("Primary date column name", default="InvoiceDate")
has_fallback = ask_question("Do you have fallback date columns (Month, Year)?", default="yes", validator=validate_yes_no)
if has_fallback:
fallback_str = ask_question("Fallback date columns (comma-separated)", default="Month, Year")
responses['date_fallback'] = [col.strip() for col in fallback_str.split(',')]
else:
responses['date_fallback'] = []
responses['customer_column'] = ask_question("Customer/Account column name", default="Customer")
responses['item_column'] = ask_question("Item/Product column name", default="Item")
has_quantity = ask_question("Do you have a Quantity column?", default="yes", validator=validate_yes_no)
if has_quantity:
responses['quantity_column'] = ask_question("Quantity column name", default="Quantity")
else:
responses['quantity_column'] = None
# Date Range
print_header("Date Range Configuration")
responses['min_year'] = ask_question("Minimum year to include in analysis", default="2021", validator=validate_int)
responses['max_date'] = ask_question("Maximum date (YYYY-MM-DD)", default="2025-09-30")
years_str = ask_question("Analysis years (comma-separated, e.g., 2021,2022,2023,2024,2025)", default="2021,2022,2023,2024,2025")
responses['analysis_years'] = [int(y.strip()) for y in years_str.split(',')]
# LTM Configuration
print_header("LTM (Last Twelve Months) Configuration")
print("LTM is used for the most recent partial year to enable apples-to-apples comparison.")
print("Example: If your latest data is through September 2025, use Oct 2024 - Sep 2025.\n")
use_ltm = ask_question("Do you need LTM for the most recent year?", default="yes", validator=validate_yes_no)
responses['ltm_enabled'] = use_ltm
if use_ltm:
responses['ltm_start_month'] = ask_question("LTM start month (1-12)", default="10", validator=validate_int)
responses['ltm_start_year'] = ask_question("LTM start year", default="2024", validator=validate_int)
responses['ltm_end_month'] = ask_question("LTM end month (1-12)", default="9", validator=validate_int)
responses['ltm_end_year'] = ask_question("LTM end year", default="2025", validator=validate_int)
else:
responses['ltm_start_month'] = 10
responses['ltm_start_year'] = 2024
responses['ltm_end_month'] = 9
responses['ltm_end_year'] = 2025
# Exclusion Filters
print_header("Exclusion Filters (Optional)")
use_exclusions = ask_question("Do you need to exclude specific segments (e.g., test accounts, business units)?", default="no", validator=validate_yes_no)
responses['exclusions_enabled'] = use_exclusions
if use_exclusions:
responses['exclude_column'] = ask_question("Column name to filter on", default="Country")
exclude_values_str = ask_question("Values to exclude (comma-separated)", default="")
responses['exclude_values'] = [v.strip() for v in exclude_values_str.split(',') if v.strip()]
else:
responses['exclude_column'] = None
responses['exclude_values'] = []
# Generate config.py
print_header("Generating Configuration")
print("Updating config.py with your settings...")
# Read current config.py
config_path = Path('config.py')
if not config_path.exists():
print("ERROR: config.py not found!")
return
with open(config_path, 'r', encoding='utf-8') as f:
config_content = f.read()
# Replace values
replacements = {
"COMPANY_NAME = \"Your Company Name\"": f"COMPANY_NAME = \"{responses['company_name']}\"",
"ANALYSIS_DATE = \"2026-01-12\"": f"ANALYSIS_DATE = \"{responses['analysis_date']}\"",
"DATA_FILE = 'sales_data.csv'": f"DATA_FILE = '{responses['data_file']}'",
"REVENUE_COLUMN = 'USD'": f"REVENUE_COLUMN = '{responses['revenue_column']}'",
"DATE_COLUMN = 'InvoiceDate'": f"DATE_COLUMN = '{responses['date_column']}'",
"DATE_FALLBACK_COLUMNS = ['Month', 'Year']": f"DATE_FALLBACK_COLUMNS = {responses['date_fallback']}",
"CUSTOMER_COLUMN = 'Customer'": f"CUSTOMER_COLUMN = '{responses['customer_column']}'",
"ITEM_COLUMN = 'Item'": f"ITEM_COLUMN = '{responses['item_column']}'",
"QUANTITY_COLUMN = 'Quantity'": f"QUANTITY_COLUMN = '{responses['quantity_column']}'" if responses['quantity_column'] else "QUANTITY_COLUMN = None",
"MIN_YEAR = 2021": f"MIN_YEAR = {responses['min_year']}",
"MAX_DATE = pd.Timestamp('2025-09-30')": f"MAX_DATE = pd.Timestamp('{responses['max_date']}')",
"ANALYSIS_YEARS = [2021, 2022, 2023, 2024, 2025]": f"ANALYSIS_YEARS = {responses['analysis_years']}",
"LTM_ENABLED = True": f"LTM_ENABLED = {responses['ltm_enabled']}",
"LTM_START_MONTH = 10": f"LTM_START_MONTH = {responses['ltm_start_month']}",
"LTM_START_YEAR = 2024": f"LTM_START_YEAR = {responses['ltm_start_year']}",
"LTM_END_MONTH = 9": f"LTM_END_MONTH = {responses['ltm_end_month']}",
"LTM_END_YEAR = 2025": f"LTM_END_YEAR = {responses['ltm_end_year']}",
}
# Handle exclusions
if responses['exclusions_enabled']:
exclusions_config = f"""EXCLUSION_FILTERS = {{
'enabled': True,
'exclude_by_column': '{responses['exclude_column']}',
'exclude_values': {responses['exclude_values']}
}}"""
# Replace the exclusion filters section
import re
pattern = r"EXCLUSION_FILTERS = \{.*?\}"
config_content = re.sub(pattern, exclusions_config, config_content, flags=re.DOTALL)
else:
exclusions_config = """EXCLUSION_FILTERS = {
'enabled': False,
'exclude_by_column': None,
'exclude_values': []
}"""
import re
pattern = r"EXCLUSION_FILTERS = \{.*?\}"
config_content = re.sub(pattern, exclusions_config, config_content, flags=re.DOTALL)
# Apply replacements
for old, new in replacements.items():
if old in config_content:
config_content = config_content.replace(old, new)
# Write updated config
with open(config_path, 'w', encoding='utf-8') as f:
f.write(config_content)
print(" ✓ Configuration updated successfully!")
# Summary
print_header("Setup Complete")
print("Your configuration has been saved to config.py")
print("\nNext steps:")
print("1. Place your data file in the template directory (if not already there)")
print("2. Test data loading: python -c \"from data_loader import load_sales_data; from config import get_data_path; df = load_sales_data(get_data_path()); print(f'Loaded {len(df):,} rows')\"")
print("3. Review config.py and adjust any settings as needed")
print("4. Start creating your analysis scripts using analysis_template.py")
print("\nFor help, see README.md")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\n\nSetup cancelled by user.")
sys.exit(0)