# Configuration Manager

## Interface Definition

**Interface Name**: `IConfigurationManager`

### Interface Methods

```python
class IConfigurationManager(ABC):
    @abstractmethod
    def load_config(self, config_path: str) -> SystemConfig:
        pass
    
    @abstractmethod
    def get_camera_params(self, camera_id: Optional[str] = None) -> CameraParameters:
        pass
    
    @abstractmethod
    def validate_config(self, config: SystemConfig) -> ValidationResult:
        pass
    
    @abstractmethod
    def get_flight_config(self, flight_id: str) -> FlightConfig:
        pass
    
    @abstractmethod
    def update_config(self, section: str, key: str, value: Any) -> bool:
        pass
    
    @abstractmethod
    def get_operational_altitude(self, flight_id: str) -> float:
        """Returns predefined operational altitude for the flight (NOT from EXIF)."""
        pass
    
    @abstractmethod
    def get_frame_spacing(self, flight_id: str) -> float:
        """Returns expected distance between consecutive frames in meters."""
        pass
    
    @abstractmethod
    def save_flight_config(self, flight_id: str, config: FlightConfig) -> bool:
        """Persists flight-specific configuration."""
        pass
```

## Component Description

### Responsibilities
- Load system configuration from files/environment
- Provide camera parameters (focal length, sensor size, resolution)
- Manage flight-specific configurations
- Validate configuration integrity
- Support configuration updates at runtime

### Scope
- System-wide configuration
- Camera parameter management
- Operational area bounds
- Model paths and settings
- Database connections
- API endpoints

## API Methods

### `load_config(config_path: str) -> SystemConfig`

**Description**: Loads system configuration.

**Called By**: System startup

**Input**: `config_path: str` - Path to config file (YAML/JSON)

**Output**: `SystemConfig` - Complete configuration

**Test Cases**:
1. Load valid config → succeeds
2. Missing file → uses defaults
3. Invalid config → raises error

---

### `get_camera_params(camera_id: Optional[str] = None) -> CameraParameters`

**Description**: Gets camera parameters.

**Called By**: All processing components

**Output**:
```python
CameraParameters:
    focal_length: float
    sensor_width: float
    sensor_height: float
    resolution_width: int
    resolution_height: int
    principal_point: Tuple[float, float]
    distortion_coefficients: List[float]
```

**Test Cases**:
1. Get default camera → returns params
2. Get specific camera → returns params

---

### `validate_config(config: SystemConfig) -> ValidationResult`

**Description**: Validates configuration.

**Called By**: After load_config

**Validation Rules**:
- Camera parameters sensible
- Paths exist
- Operational area valid
- Database connection string valid

**Test Cases**:
1. Valid config → passes
2. Invalid focal length → fails

---

### `get_flight_config(flight_id: str) -> FlightConfig`

**Description**: Gets flight-specific configuration.

**Called By**: F02.1 Flight Lifecycle Manager

**Output**:
```python
FlightConfig:
    camera_params: CameraParameters
    altitude: float
    operational_area: OperationalArea
```

**Test Cases**:
1. Get flight config → returns params

---

### `update_config(section: str, key: str, value: Any) -> bool`

**Description**: Updates config at runtime.

**Called By**: Admin tools

**Test Cases**:
1. Update value → succeeds
2. Invalid key → fails

---

### `get_operational_altitude(flight_id: str) -> float`

**Description**: Returns predefined operational altitude for the flight in meters. This is the altitude provided during flight creation, NOT extracted from EXIF metadata (images don't have GPS/altitude metadata per problem constraints).

**Called By**:
- F10 Factor Graph Optimizer (for scale resolution)
- H02 GSD Calculator (for GSD computation)
- F09 Metric Refinement (for alignment)

**Input**: `flight_id: str`

**Output**: `float` - Altitude in meters (typically 100-500m)

**Test Cases**:
1. Get existing flight altitude → returns value
2. Non-existent flight → raises error

---

### `get_frame_spacing(flight_id: str) -> float`

**Description**: Returns expected distance between consecutive frames in meters. Used for scale estimation in visual odometry.

**Called By**:
- F10 Factor Graph Optimizer (for expected displacement calculation)

**Input**: `flight_id: str`

**Output**: `float` - Expected frame spacing in meters (typically ~100m)

**Test Cases**:
1. Get frame spacing → returns expected distance

---

### `save_flight_config(flight_id: str, config: FlightConfig) -> bool`

**Description**: Persists flight-specific configuration when a flight is created.

**Called By**:
- F02.1 Flight Lifecycle Manager (during flight creation)

**Input**:
```python
flight_id: str
config: FlightConfig
```

**Output**: `bool` - True if saved successfully

**Test Cases**:
1. Save valid config → succeeds
2. Invalid flight_id → fails

## Data Models

### SystemConfig
```python
class SystemConfig(BaseModel):
    camera: CameraParameters
    operational_area: OperationalArea
    models: ModelPaths
    database: DatabaseConfig
    api: APIConfig
```

### CameraParameters
```python
class CameraParameters(BaseModel):
    focal_length: float  # mm
    sensor_width: float  # mm
    sensor_height: float  # mm
    resolution_width: int
    resolution_height: int
    principal_point: Tuple[float, float]
    distortion_coefficients: List[float]
```

### OperationalArea
```python
class OperationalArea(BaseModel):
    name: str = "Eastern Ukraine"
    min_lat: float = 45.0
    max_lat: float = 52.0
    min_lon: float = 22.0
    max_lon: float = 40.0
```

### RecoveryConfig
```python
class RecoveryConfig(BaseModel):
    """Configuration for failure recovery and progressive search."""
    search_grid_sizes: List[int] = [1, 4, 9, 16, 25]  # Progressive tile search grid
    min_chunk_frames_for_matching: int = 5  # Minimum frames before chunk matching
    max_chunk_frames_for_matching: int = 20  # Maximum frames per chunk
    user_input_threshold_tiles: int = 25  # Request user input after this many tiles
    chunk_matching_interval_seconds: float = 5.0  # Background matching interval
    confidence_threshold_good: float = 0.7  # Confidence for good tracking
    confidence_threshold_degraded: float = 0.5  # Confidence for degraded tracking
    min_inlier_count_good: int = 50  # Inliers for good tracking
    min_inlier_count_tracking: int = 20  # Minimum inliers before tracking loss
```

### RotationConfig
```python
class RotationConfig(BaseModel):
    """Configuration for image rotation and heading tracking."""
    rotation_step_degrees: float = 30.0  # Degrees per rotation step
    litesam_max_rotation_tolerance: float = 45.0  # Max rotation LiteSAM handles
    sharp_turn_threshold_degrees: float = 45.0  # Threshold for sharp turn detection
    heading_history_size: int = 10  # Number of headings to track
    confidence_threshold: float = 0.7  # For accepting rotation match
    
    @property
    def rotation_iterations(self) -> int:
        """Number of rotation steps (360 / step_degrees)."""
        return int(360 / self.rotation_step_degrees)
```

## Dependencies

### Internal Components
- **F03 Flight Database**: For flight-specific configuration persistence (save_flight_config stores to F03)

### External Dependencies
- **pydantic**: Data model validation
- **PyYAML**: Configuration file parsing

