File size: 5,247 Bytes
cf739bf
2817797
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
cf739bf
 
 
 
 
 
 
 
 
9562799
 
cf739bf
 
9562799
 
cf739bf
 
9562799
 
 
 
 
 
 
 
 
 
 
 
cf739bf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
"""
================================================================================
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