#!/usr/bin/env python """ Batch runner for quant report with different filter combinations. Runs 03_quant_report.script.py for each single-filter combination: - Each age group (with all others active) - Each gender (with all others active) - Each ethnicity (with all others active) - Each income group (with all others active) - Each consumer segment (with all others active) Usage: uv run python run_filter_combinations.py uv run python run_filter_combinations.py --dry-run # Preview combinations without running """ import subprocess import sys import json from pathlib import Path from tqdm import tqdm from utils import QualtricsSurvey # Default data paths (same as in 03_quant_report.script.py) RESULTS_FILE = 'data/exports/2-2-26/JPMC_Chase Brand Personality_Quant Round 1_February 2, 2026_Labels.csv' QSF_FILE = 'data/exports/OneDrive_2026-01-21/Soft Launch Data/JPMC_Chase_Brand_Personality_Quant_Round_1.qsf' REPORT_SCRIPT = Path(__file__).parent / '03_quant_report.script.py' def get_filter_combinations(survey: QualtricsSurvey) -> list[dict]: """ Generate all single-filter combinations. Each combination isolates ONE filter value while keeping all others at "all selected". Returns list of dicts with filter kwargs for each run. """ combinations = [] # Add "All Respondents" run (no filters = all options selected) combinations.append({ 'name': 'All_Respondents', 'filters': {} # Empty = use defaults (all selected) }) # Age groups - one at a time for age in survey.options_age: combinations.append({ 'name': f'Age-{age}', 'filters': {'age': [age]} }) # Gender - one at a time for gender in survey.options_gender: combinations.append({ 'name': f'Gender-{gender}', 'filters': {'gender': [gender]} }) # Ethnicity - one at a time for ethnicity in survey.options_ethnicity: combinations.append({ 'name': f'Ethnicity-{ethnicity}', 'filters': {'ethnicity': [ethnicity]} }) # Income - one at a time for income in survey.options_income: combinations.append({ 'name': f'Income-{income}', 'filters': {'income': [income]} }) # Consumer segments - one at a time for consumer in survey.options_consumer: combinations.append({ 'name': f'Consumer-{consumer}', 'filters': {'consumer': [consumer]} }) return combinations def run_report(filters: dict, dry_run: bool = False) -> bool: """ Run the report script with given filters. Args: filters: Dict of filter_name -> list of values dry_run: If True, just print command without running Returns: True if successful, False otherwise """ cmd = [sys.executable, str(REPORT_SCRIPT)] for filter_name, values in filters.items(): if values: cmd.extend([f'--{filter_name}', json.dumps(values)]) if dry_run: print(f" Would run: {' '.join(cmd)}") return True try: result = subprocess.run( cmd, capture_output=True, text=True, cwd=Path(__file__).parent ) if result.returncode != 0: print(f"\n ERROR: {result.stderr[:500]}") return False return True except Exception as e: print(f"\n ERROR: {e}") return False def main(): import argparse parser = argparse.ArgumentParser(description='Run quant report for all filter combinations') parser.add_argument('--dry-run', action='store_true', help='Preview combinations without running') args = parser.parse_args() # Load survey to get available filter options print("Loading survey to get filter options...") survey = QualtricsSurvey(RESULTS_FILE, QSF_FILE) survey.load_data() # Populates options_* attributes # Generate all combinations combinations = get_filter_combinations(survey) print(f"Generated {len(combinations)} filter combinations") if args.dry_run: print("\nDRY RUN - Commands that would be executed:") for combo in combinations: print(f"\n{combo['name']}:") run_report(combo['filters'], dry_run=True) return # Run each combination with progress bar successful = 0 failed = [] for combo in tqdm(combinations, desc="Running reports", unit="filter"): tqdm.write(f"Running: {combo['name']}") if run_report(combo['filters']): successful += 1 else: failed.append(combo['name']) # Summary print(f"\n{'='*50}") print(f"Completed: {successful}/{len(combinations)} successful") if failed: print(f"Failed: {', '.join(failed)}") if __name__ == '__main__': main()