# Route Chunk Manager

## Interface Definition

**Interface Name**: `IRouteChunkManager`

### Interface Methods

```python
class IRouteChunkManager(ABC):
    @abstractmethod
    def create_chunk(self, flight_id: str, start_frame_id: int) -> ChunkHandle:
        pass
    
    @abstractmethod
    def add_frame_to_chunk(self, chunk_id: str, frame_id: int, vo_result: RelativePose) -> bool:
        pass
    
    @abstractmethod
    def get_chunk_frames(self, chunk_id: str) -> List[int]:
        pass
    
    @abstractmethod
    def get_chunk_images(self, chunk_id: str) -> List[np.ndarray]:
        pass
    
    @abstractmethod
    def get_chunk_composite_descriptor(self, chunk_id: str) -> np.ndarray:
        pass
    
    @abstractmethod
    def get_chunk_bounds(self, chunk_id: str) -> ChunkBounds:
        pass
    
    @abstractmethod
    def is_chunk_ready_for_matching(self, chunk_id: str) -> bool:
        pass
    
    @abstractmethod
    def mark_chunk_anchored(self, chunk_id: str, frame_id: int, gps: GPSPoint) -> bool:
        """
        Transactional update:
        1. Calls F10.add_chunk_anchor().
        2. IF success: Updates internal state to 'anchored'.
        3. Returns success status.
        """
        pass
    
    @abstractmethod
    def get_chunks_for_matching(self, flight_id: str) -> List[ChunkHandle]:
        pass
    
    @abstractmethod
    def get_active_chunk(self, flight_id: str) -> Optional[ChunkHandle]:
        pass
    
    @abstractmethod
    def deactivate_chunk(self, chunk_id: str) -> bool:
        pass
    
    @abstractmethod
    def merge_chunks(self, main_chunk_id: str, new_chunk_id: str, transform: Sim3Transform) -> bool:
        """
        Merges new_chunk INTO main_chunk. Extends main_chunk with new_chunk's frames.
        
        Transactional update:
        1. Calls F10.merge_chunk_subgraphs(flight_id, new_chunk_id, main_chunk_id, transform).
        2. IF success: Updates internal state (new_chunk merged/deactivated).
        3. Returns success status.
        
        Note: flight_id is obtained from the ChunkHandle stored internally for main_chunk_id.
        """
        pass
    
    @abstractmethod
    def mark_chunk_matching(self, chunk_id: str) -> bool:
        pass
    
    @abstractmethod
    def save_chunk_state(self, flight_id: str) -> bool:
        pass
    
    @abstractmethod
    def load_chunk_state(self, flight_id: str) -> bool:
        pass
```

## Component Description

### Responsibilities
- **Source of Truth**: Manages chunk states (Active, Matching, Anchored, Merged).
- **Transactional Integrity**: Ensures internal state updates are atomic with respect to Factor Graph (F10) operations.
- **Implementation**: Uses "Check-Act" pattern. Calls F10 first; if F10 fails, F12 does not update state and returns error.

### Internal State Management
F12 maintains an internal dictionary of `ChunkHandle` objects keyed by `chunk_id`. Each `ChunkHandle` contains `flight_id`, allowing F12 to resolve flight context for F10 calls without requiring `flight_id` as a parameter on every method. This simplifies the API for callers while maintaining flight isolation internally.

```python
# Internal state
_chunks: Dict[str, ChunkHandle]  # chunk_id -> ChunkHandle (contains flight_id)
```

### Interaction
- Called by **F02.2 Flight Processing Engine** and **F11 Failure Recovery Coordinator** (via F02.2 or direct delegation).
- Calls **F10 Factor Graph Optimizer**.

### Scope
- Chunk lifecycle management
- Chunk state tracking
- Chunk representation generation (descriptors, bounds)
- Integration point for chunk matching coordination

## API Methods

### `create_chunk(flight_id: str, start_frame_id: int) -> ChunkHandle`

**Description**: Initializes a new route chunk.

**Called By**:
- F02.2 Flight Processing Engine (when tracking lost)
- F11 Failure Recovery Coordinator (proactive chunk creation)

**Input**:
```python
flight_id: str
start_frame_id: int  # First frame in chunk
```

**Output**:
```python
ChunkHandle:
    chunk_id: str
    flight_id: str
    start_frame_id: int
    end_frame_id: Optional[int]
    frames: List[int]
    is_active: bool
    has_anchor: bool
    anchor_frame_id: Optional[int]
    anchor_gps: Optional[GPSPoint]
    matching_status: str
```

**Processing Flow**:
1. Generate unique chunk_id
2. Call F10 Factor Graph Optimizer.create_new_chunk()
3. Initialize chunk state (unanchored, active)
4. Store chunk metadata
5. Return ChunkHandle

**Test Cases**:
1. **Create chunk**: Returns ChunkHandle with is_active=True
2. **Multiple chunks**: Can create multiple chunks for same flight
3. **Chunk initialization**: Chunk initialized in factor graph

---

### `add_frame_to_chunk(chunk_id: str, frame_id: int, vo_result: RelativePose) -> bool`

**Description**: Adds a frame to an active chunk.

**Called By**:
- F02.2 Flight Processing Engine (during frame processing)

**Input**:
```python
chunk_id: str
frame_id: int
vo_result: RelativePose  # From F07 Sequential VO
```

**Output**:
```python
bool: True if frame added successfully
```

**Processing Flow**:
1. Verify chunk exists and is active
2. Add frame_id to chunk's frames list
3. Store vo_result for chunk
4. Call F10.add_relative_factor_to_chunk()
5. Update chunk's end_frame_id
6. Check if chunk ready for matching

**Test Cases**:
1. **Add frame to active chunk**: Frame added successfully
2. **Add frame to inactive chunk**: Returns False
3. **Chunk growth**: Chunk frames list updated

---

### `get_chunk_frames(chunk_id: str) -> List[int]`

**Description**: Retrieves list of frame IDs in a chunk.

**Called By**:
- F08 Global Place Recognition (for chunk descriptor computation)
- F09 Metric Refinement (for chunk LiteSAM matching)
- F11 Failure Recovery Coordinator (chunk state queries)

**Input**: `chunk_id: str`

**Output**: `List[int]`  # Frame IDs in chunk, ordered by sequence

---

### `get_chunk_images(chunk_id: str) -> List[np.ndarray]`

**Description**: Retrieves images for all frames in a chunk.

**Called By**:
- F08 Global Place Recognition (chunk descriptor computation)
- F09 Metric Refinement (chunk LiteSAM matching)
- F06 Image Rotation Manager (chunk rotation)

**Output**: `List[np.ndarray]`  # Images for each frame in chunk

---

### `get_chunk_composite_descriptor(chunk_id: str) -> np.ndarray`

**Description**: Computes aggregate DINOv2 descriptor for chunk (for semantic matching).

**Called By**:
- F08 Global Place Recognition (chunk semantic matching)

**Output**: `np.ndarray`: Aggregated descriptor vector (4096-dim or 8192-dim)

---

### `get_chunk_bounds(chunk_id: str) -> ChunkBounds`

**Description**: Estimates GPS bounds of a chunk based on VO trajectory.

**Called By**:
- F11 Failure Recovery Coordinator (for tile search area)
- F04 Satellite Data Manager (for tile prefetching)

**Output**:
```python
ChunkBounds:
    estimated_center: GPSPoint
    estimated_radius: float  # meters
    confidence: float
```

---

### `is_chunk_ready_for_matching(chunk_id: str) -> bool`

**Description**: Checks if a chunk has enough data (frames, spread) to attempt satellite matching.

**Criteria**:
- Min frames: >= 5 frames (configurable)
- Max frames: <= 20 frames (configurable, prevents oversized chunks)
- Internal consistency: VO factors have reasonable inlier counts
- Not already matched: matching_status != "anchored" or "merged"

---

### `mark_chunk_anchored(chunk_id: str, frame_id: int, gps: GPSPoint) -> bool`

**Description**: Anchors a chunk to a specific GPS coordinate (e.g., from successful satellite matching).

**Called By**:
- F11 Failure Recovery Coordinator (after successful chunk matching)

**Input**:
```python
chunk_id: str
frame_id: int  # Frame within chunk that was anchored
gps: GPSPoint
```

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

**Processing Flow**:
1. Verify chunk exists
2. Call F10.add_chunk_anchor()
3. If successful:
   - Update chunk state (has_anchor=True, anchor_frame_id, anchor_gps)
   - Update matching_status to "anchored"
   - Trigger chunk optimization

**Test Cases**:
1. **Mark anchored**: Chunk state updated correctly
2. **Anchor in factor graph**: F10 anchor added
3. **Chunk optimization**: Chunk optimized after anchoring

---

### `get_chunks_for_matching(flight_id: str) -> List[ChunkHandle]`

**Description**: Retrieves all unanchored chunks ready for matching.

**Called By**:
- F11 Failure Recovery Coordinator (background matching task)

**Output**: `List[ChunkHandle]`  # Unanchored chunks ready for matching

---

### `get_active_chunk(flight_id: str) -> Optional[ChunkHandle]`

**Description**: Gets the currently active chunk for a flight.

**Called By**:
- F02.2 Flight Processing Engine (before processing frame)

**Output**: `Optional[ChunkHandle]`  # Active chunk or None

---

### `deactivate_chunk(chunk_id: str) -> bool`

**Description**: Deactivates a chunk (typically after merging or completion).

**Called By**:
- F11 Failure Recovery Coordinator (after chunk merged)
- F02.2 Flight Processing Engine (chunk lifecycle)

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

---

### `merge_chunks(main_chunk_id: str, new_chunk_id: str, transform: Sim3Transform) -> bool`

**Description**: Merges new_chunk INTO main_chunk. The resulting merged chunk is main_chunk (extended with new_chunk's frames). new_chunk is deactivated after merge.

**Called By**:
- F11 Failure Recovery Coordinator (after successful chunk matching)

**Input**:
```python
main_chunk_id: str  # Main chunk being extended (destination, typically older/established trajectory)
new_chunk_id: str   # New chunk being merged in (source, typically newer/recently anchored)
transform: Sim3Transform:
    translation: np.ndarray  # (3,)
    rotation: np.ndarray  # (3, 3) or quaternion
    scale: float
```

**Output**: `bool` - True if merge successful

**flight_id Resolution**: F12 internally stores ChunkHandle objects keyed by chunk_id. The flight_id is extracted from the ChunkHandle for main_chunk_id when calling F10 methods.

**Processing Flow**:
1. Get flight_id from internally stored ChunkHandle for main_chunk_id
2. Call F10.merge_chunk_subgraphs(flight_id, new_chunk_id, main_chunk_id, transform)
3. If successful:
   - Update new_chunk_id state:
     - Set is_active=False
     - Set matching_status="merged"
     - Call deactivate_chunk(new_chunk_id)
   - main_chunk remains active (now contains merged frames)
   - Persist chunk state via F03 Flight Database.save_chunk_state()
4. Return True

**Test Cases**:
1. **Merge anchored chunk**: new_chunk merged into main_chunk
2. **New chunk deactivated**: new_chunk marked as merged and deactivated
3. **Main chunk extended**: main_chunk remains active with additional frames

---

### `mark_chunk_matching(chunk_id: str) -> bool`

**Description**: Explicitly marks chunk as being matched (updates matching_status to "matching").

**Called By**:
- F11 Failure Recovery Coordinator (when chunk matching starts)

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

## Integration Tests

### Test 1: Chunk Lifecycle
1. create_chunk() → chunk created
2. add_frame_to_chunk() × 10 → 10 frames added
3. is_chunk_ready_for_matching() → True
4. mark_chunk_anchored() → chunk anchored
5. deactivate_chunk() → chunk deactivated

### Test 2: Chunk Descriptor Computation
1. Create chunk with 10 frames
2. get_chunk_images() → 10 images
3. get_chunk_composite_descriptor() → aggregated descriptor
4. Verify descriptor more robust than single-image descriptor

### Test 3: Multiple Chunks
1. Create chunk_1 (frames 1-10)
2. Create chunk_2 (frames 20-30)
3. get_chunks_for_matching() → returns both chunks
4. mark_chunk_anchored(chunk_1) → chunk_1 anchored
5. get_chunks_for_matching() → returns only chunk_2

### Test 4: Chunk Merging
1. Create main_chunk (frames 1-10), new_chunk (frames 20-30)
2. Anchor new_chunk via mark_chunk_anchored()
3. merge_chunks(main_chunk, new_chunk, transform) → new_chunk merged into main_chunk
4. Verify new_chunk marked as merged and deactivated
5. Verify main_chunk extended with new frames
6. Verify F10.merge_chunk_subgraphs() called with correct parameters

### Test 5: Chunk Matching Status
1. Create chunk
2. mark_chunk_matching() → status updated to "matching"
3. mark_chunk_anchored() → status updated to "anchored"
4. Verify explicit state transitions

## Non-Functional Requirements

### Performance
- **create_chunk**: < 10ms
- **add_frame_to_chunk**: < 5ms
- **get_chunk_composite_descriptor**: < 3s for 20 images (async)
- **get_chunk_bounds**: < 10ms

### Reliability
- Chunk state persisted across restarts
- Graceful handling of missing frames
- Thread-safe chunk operations

## Dependencies

### Internal Components
- **F10 Factor Graph Optimizer**: Critical dependency for subgraph operations (`create_chunk_subgraph`, `add_relative_factor_to_chunk`, `merge_chunk_subgraphs`).
- **F03 Flight Database**: Persistence via `save_chunk_state()`, `load_chunk_states()`.
- **F05 Image Input Pipeline**: Image retrieval via `get_image_by_sequence()` for `get_chunk_images()`.
- **F08 Global Place Recognition**: Descriptor computation via `compute_chunk_descriptor()` for `get_chunk_composite_descriptor()`.

## Data Models

### ChunkHandle
```python
class ChunkHandle(BaseModel):
    chunk_id: str
    flight_id: str
    start_frame_id: int
    end_frame_id: Optional[int]
    frames: List[int]
    is_active: bool
    has_anchor: bool
    anchor_frame_id: Optional[int]
    anchor_gps: Optional[GPSPoint]
    matching_status: str  # "unanchored", "matching", "anchored", "merged"
```

### ChunkBounds
```python
class ChunkBounds(BaseModel):
    estimated_center: GPSPoint
    estimated_radius: float  # meters
    confidence: float  # 0.0 to 1.0
```

### ChunkConfig
```python
class ChunkConfig(BaseModel):
    min_frames_for_matching: int = 5
    max_frames_per_chunk: int = 20
    descriptor_aggregation: str = "mean"  # "mean", "vlad", "max"
```

### Sim3Transform
```python
class Sim3Transform(BaseModel):
    translation: np.ndarray  # (3,) - translation vector
    rotation: np.ndarray  # (3, 3) rotation matrix or (4,) quaternion
    scale: float  # Scale factor
```
