""" ================================================================================ CONFIGURATION LOADER - Centralized Settings Management ================================================================================ PURPOSE: Single-point configuration loading for the entire application. Reads config.yaml once at startup, caches the result, and resolves relative paths to absolute paths for consistent behavior regardless of working directory. KEY RESPONSIBILITIES: 1. Parse config.yaml (YAML format) 2. Cache configuration globally (singleton pattern) 3. Resolve relative paths to absolute paths 4. Support both project-root and working-directory lookups 5. Provide consistent paths for models, data, and ML artifacts CONFIGURATION STRUCTURE (config.yaml): graph: # Graph analysis settings pagerank_alpha: 0.85 # PageRank damping factor pagerank_max_iter: 100 # PageRank iterations betweenness_k: auto # Betweenness centrality sampling ml: # Machine learning configuration model_path: models/... # Scikit-learn model location scaler_path: models/... # Feature scaler pickle gnn_model_path: models/... # PyTorch GNN weights feature_columns: [...] # Expected input features data: # Data paths raw_path: data/raw/ # Raw transaction CSV processed_path: data/proc/ # Processed data transactions_path: data/... # Transaction database alerts_db_path: data/alerts.db # Alert cache detection: # Detector thresholds cycle_threshold: 3 # Min cycle length mule_threshold: 5 # Min mule members round_trip_threshold: 0.8 # Confidence cutoff api: # API configuration host: 0.0.0.0 port: 8000 debug: true PATH RESOLUTION: Relative paths are resolved relative to project root: config.yaml location → parent dir → project root Lookup strategy: 1. Try config_path = ${PROJECT_ROOT}/config.yaml 2. If not found: Try config_path = cwd/config.yaml 3. Use whichever directory contains config.yaml as base Path resolution (ml and data sections): - Absolute paths (starting with /) are used as-is - Relative paths are joined with project_root - Result: paths work regardless of cwd SINGLETON PATTERN: _config: Global cache (module-level) - None until first get_config() call - Loaded once, reused thereafter - Thread-safe for read operations - (Update requires module reload) PERFORMANCE: - Single YAML parse (not per-request) - Cached in memory - Path resolution happens once at load time - No file I/O after initial load DEPENDENCIES: - PyYAML: YAML parsing - os: Path manipulation USAGE EXAMPLE: from src.config_loader import get_config # First call loads and caches cfg = get_config() # Subsequent calls return cached result cfg = get_config() # No file I/O, instant # Access settings pagerank_alpha = cfg['graph']['pagerank_alpha'] model_path = cfg['ml']['model_path'] # Absolute path # Create loader with config loader = DataLoader(cfg['data']['raw_path']) predictor = HybridPredictor() predictor.load_models() # Uses cfg['ml']['...'] CONFIGURATION BEST PRACTICES: 1. Store config.yaml in project root (not in src/) 2. Use relative paths (so it works in any deployment) 3. Never hardcode paths in code 4. Use get_config() for all settings 5. Document all config options in config.yaml ERROR HANDLING: - FileNotFoundError if config.yaml not found - yaml.YAMLError if YAML syntax invalid - KeyError if accessing non-existent config key (handle explicitly) NOTES: - Configuration is read-only after startup - To change config: restart the application - Environment variables can override (if added to loader) - No validation of config values (application code must check) ================================================================================ """ import yaml import os _config = None def get_config() -> dict: global _config if _config is None: project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) config_path = os.path.join(project_root, 'config.yaml') if not os.path.exists(config_path): config_path = 'config.yaml' project_root = os.getcwd() with open(config_path, 'r') as f: _config = yaml.safe_load(f) # Resolve relative model/data paths to absolute so they work regardless of cwd if 'ml' in _config: for key in ('model_path', 'scaler_path', 'gnn_model_path'): if key in _config['ml'] and not os.path.isabs(_config['ml'][key]): _config['ml'][key] = os.path.join(project_root, _config['ml'][key]) if 'data' in _config: for key in ('raw_path', 'processed_path', 'transactions_path', 'alerts_db_path'): if key in _config['data'] and not os.path.isabs(_config['data'][key]): _config['data'][key] = os.path.join(project_root, _config['data'][key]) return _config