File size: 6,572 Bytes
8e0e3e3
0b88148
 
8e0e3e3
0b88148
 
 
23c10b8
8e0e3e3
23c10b8
8e0e3e3
0b88148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23c10b8
0b88148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23c10b8
0b88148
 
 
 
 
 
23c10b8
 
0b88148
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23c10b8
 
0b88148
 
 
23c10b8
8e0e3e3
0b88148
 
 
 
 
 
 
 
 
 
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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
"""
Configuration management for ARF Demo
Updated for better Pydantic compatibility and fallback handling
"""
from typing import Optional, Dict, Any, List
from enum import Enum
import os
import logging

logger = logging.getLogger(__name__)

# Try to import from pydantic-settings, fallback to pydantic
try:
    from pydantic_settings import BaseSettings
    from pydantic import Field, field_validator, ConfigDict
    PYDANTIC_V2 = True
    logger.info("Using pydantic-settings for BaseSettings")
except ImportError:
    try:
        from pydantic import BaseSettings, Field, validator
        PYDANTIC_V2 = False
        logger.info("Using pydantic.BaseSettings (older version)")
    except ImportError as e:
        logger.warning(f"Failed to import pydantic: {e}. Using fallback settings.")
        # Create minimal fallback
        class BaseSettings:
            model_config = {}
            def __init__(self, **kwargs):
                for k, v in kwargs.items():
                    setattr(self, k, v)
        
        class Field:
            @staticmethod
            def default(value):
                return value
        
        def validator(*args, **kwargs):
            def decorator(func):
                return func
            return decorator
        
        def field_validator(*args, **kwargs):
            def decorator(func):
                return func
            return decorator
        
        PYDANTIC_V2 = False


class ARFMode(str, Enum):
    """ARF operation modes"""
    DEMO = "demo"
    OSS = "oss"
    ENTERPRISE = "enterprise"


class SafetyMode(str, Enum):
    """Safety modes for execution"""
    ADVISORY = "advisory"
    APPROVAL = "approval"
    AUTONOMOUS = "autonomous"


class Settings(BaseSettings):
    """
    Application settings with environment variable support
    """
    
    # ===== System Mode =====
    arf_mode: ARFMode = Field(
        default=ARFMode.DEMO,
        description="ARF operation mode"
    )
    
    use_mock_arf: bool = Field(
        default=True,
        description="Use mock ARF implementation (for demo mode)"
    )
    
    # ===== ARF Configuration =====
    arf_api_key: Optional[str] = Field(
        default=None,
        description="ARF API key for real integration"
    )
    
    arf_base_url: str = Field(
        default="https://api.arf.dev",
        description="ARF API base URL"
    )
    
    # ===== Business Configuration =====
    engineer_hourly_rate: float = Field(
        default=150.0,
        description="Engineer hourly rate in USD"
    )
    
    engineer_annual_cost: float = Field(
        default=125000.0,
        description="Engineer annual cost in USD"
    )
    
    default_savings_rate: float = Field(
        default=0.82,
        description="Default savings rate with ARF"
    )
    
    # ===== UI Configuration =====
    auto_refresh_seconds: int = Field(
        default=30,
        description="Auto-refresh interval in seconds"
    )
    
    max_history_items: int = Field(
        default=100,
        description="Maximum history items to display"
    )
    
    # ===== Demo Configuration =====
    default_scenario: str = Field(
        default="Cache Miss Storm",
        description="Default incident scenario"
    )
    
    scenario_config_path: str = Field(
        default="config/scenarios",
        description="Path to scenario configuration files"
    )
    
    # ===== Safety Configuration =====
    default_safety_mode: SafetyMode = Field(
        default=SafetyMode.ADVISORY,
        description="Default safety mode"
    )
    
    require_approval: bool = Field(
        default=True,
        description="Require human approval for execution"
    )
    
    # ===== Validation =====
    if PYDANTIC_V2:
        @field_validator("arf_api_key")
        @classmethod
        def validate_api_key(cls, v: Optional[str], info) -> Optional[str]:
            if info.data.get("arf_mode") == ARFMode.ENTERPRISE and not v:
                raise ValueError("ARF API key required for Enterprise mode")
            return v
        
        @field_validator("use_mock_arf")
        @classmethod
        def validate_mock_mode(cls, v: bool, info) -> bool:
            if info.data.get("arf_mode") == ARFMode.DEMO:
                return True
            return v
    else:
        @validator("arf_api_key")
        def validate_api_key(cls, v: Optional[str], values: Dict[str, Any]) -> Optional[str]:
            if values.get("arf_mode") == ARFMode.ENTERPRISE and not v:
                raise ValueError("ARF API key required for Enterprise mode")
            return v
        
        @validator("use_mock_arf")
        def validate_mock_mode(cls, v: bool, values: Dict[str, Any]) -> bool:
            if values.get("arf_mode") == ARFMode.DEMO:
                return True
            return v
    
    # Pydantic v2 config
    if PYDANTIC_V2:
        model_config = ConfigDict(
            env_file=".env",
            env_file_encoding="utf-8",
            case_sensitive=False,
            use_enum_values=True,
            extra="ignore"
        )
    else:
        class Config:
            env_file = ".env"
            env_file_encoding = "utf-8"
            case_sensitive = False
            use_enum_values = True


# Global settings instance with robust fallback
try:
    settings = Settings()
    logger.info("Settings loaded successfully")
except Exception as e:
    logger.warning(f"Failed to load settings from .env: {e}, using defaults")
    # Provide comprehensive defaults
    settings = Settings(
        arf_mode=ARFMode.DEMO,
        use_mock_arf=True,
        arf_api_key=None,
        arf_base_url="https://api.arf.dev",
        engineer_hourly_rate=150.0,
        engineer_annual_cost=125000.0,
        default_savings_rate=0.82,
        auto_refresh_seconds=30,
        max_history_items=100,
        default_scenario="Cache Miss Storm",
        scenario_config_path="config/scenarios",
        default_safety_mode=SafetyMode.ADVISORY,
        require_approval=True
    )


def get_settings() -> Settings:
    """Get settings instance (singleton pattern)"""
    return settings


def print_settings_summary() -> None:
    """Print a summary of current settings (for debugging)"""
    summary = {
        "mode": settings.arf_mode.value,
        "mock_mode": settings.use_mock_arf,
        "default_scenario": settings.default_scenario,
        "safety_mode": settings.default_safety_mode.value,
        "requires_approval": settings.require_approval
    }
    logger.info(f"Settings summary: {summary}")