# Result Manager

## Interface Definition

**Interface Name**: `IResultManager`

### Interface Methods

```python
class IResultManager(ABC):
    @abstractmethod
    def update_frame_result(self, flight_id: str, frame_id: int, result: FrameResult) -> bool:
        """
        Atomic update:
        1. Saves result to frame_results table.
        2. Updates waypoint in waypoints table.
        3. All within a single transaction via F03.
        """
        pass
    
    @abstractmethod
    def publish_waypoint_update(self, flight_id: str, frame_id: int) -> bool:
        pass
    
    @abstractmethod
    def get_flight_results(self, flight_id: str) -> FlightResults:
        pass
    
    @abstractmethod
    def mark_refined(self, flight_id: str, refined_results: List[RefinedFrameResult]) -> bool:
        """
        Updates results for frames that have been retrospectively improved.
        
        Args:
            flight_id: Flight identifier
            refined_results: List of RefinedFrameResult containing frame_id and GPS-converted coordinates
                            (caller F02.2 converts ENU to GPS before calling this method)
        """
        pass
    
    @abstractmethod
    def get_changed_frames(self, flight_id: str, since: datetime) -> List[int]:
        pass
    
    @abstractmethod
    def update_results_after_chunk_merge(self, flight_id: str, refined_results: List[RefinedFrameResult]) -> bool:
        """
        Updates results for frames affected by chunk merge.
        
        Args:
            flight_id: Flight identifier
            refined_results: List of RefinedFrameResult with GPS-converted coordinates
                            (caller F02.2 converts ENU to GPS before calling this method)
        """
        pass
```

## Component Description

### Responsibilities
- Result consistency and publishing.
- **Atomic Updates**: Ensures consistency between normalized `waypoints` and denormalized `frame_results` via transaction requests to F03.

### Scope
- Result state management
- Flight Database integration (waypoint storage)
- SSE event triggering
- Incremental update detection
- Result persistence

## API Methods

### `update_frame_result(flight_id: str, frame_id: int, result: FrameResult) -> bool`

**Description**: Persists and publishes the result of a processed frame.

**Called By**:
- Main processing loop (after each frame)
- F10 Factor Graph (after refinement)

**Input**:
```python
flight_id: str
frame_id: int
result: FrameResult:
    gps_center: GPSPoint
    altitude: float
    heading: float
    confidence: float
    timestamp: datetime
    refined: bool
    objects: List[ObjectLocation]  # From external detector
```

**Output**: `bool` - True if updated

**Processing Flow**:
1. Construct DB transaction:
    - Insert/Update `frame_results`.
    - Update `waypoints` (latest position).
2. Call `F03.execute_transaction()`.
3. If success: call `F15.send_frame_result()`.
4. Update flight statistics.

**Test Cases**:
1. New frame result → stored and published
2. Refined result → updates existing, marks refined=True

---

### `publish_waypoint_update(flight_id: str, frame_id: int) -> bool`

**Description**: Specifically triggers an update for the waypoint visualization.

**Called By**:
- Internal (after update_frame_result)

**Input**:
```python
flight_id: str
frame_id: int
```

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

**Processing Flow**:
1. Fetch latest data.
2. Call `F15.send_frame_result()` (or a specific lightweight event).
3. Handle errors (retry if transient)

**Test Cases**:
1. Successful update → Waypoint stored in database
2. Database unavailable → logs error, continues

---

### `get_flight_results(flight_id: str) -> FlightResults`

**Description**: Retrieves all results for a flight.

**Called By**:
- F01 REST API (results endpoint)
- Testing/validation

**Input**: `flight_id: str`

**Output**:
```python
FlightResults:
    flight_id: str
    frames: List[FrameResult]
    statistics: FlightStatistics
```

**Test Cases**:
1. Get all results → returns complete trajectory

---

### `mark_refined(flight_id: str, refined_results: List[RefinedFrameResult]) -> bool`

**Description**: Updates results for frames that have been retrospectively improved (e.g., after loop closure or chunk merge).

**Called By**:
- F02.2 Flight Processing Engine (after factor graph optimization)

**Input**:
```python
flight_id: str
refined_results: List[RefinedFrameResult]  # GPS-converted results from F02.2
```

**Output**: `bool`

**Important**: F14 does NOT call F10 or F13. The caller (F02.2) performs the following steps before calling mark_refined():
1. F02.2 gets refined poses from F10.get_trajectory(flight_id) (ENU coordinates)
2. F02.2 converts ENU to GPS via F13.enu_to_gps(flight_id, enu_tuple)
3. F02.2 constructs RefinedFrameResult objects with GPS coordinates
4. F02.2 calls F14.mark_refined() with the GPS-converted results

**Processing Flow**:
1. For each refined_result in refined_results:
   - Extract frame_id, gps_center, confidence from RefinedFrameResult
   - Update result with refined=True via F03 Flight Database (part of transaction)
   - Update waypoint via F03 Flight Database (part of transaction)
   - Call F15 SSE Event Streamer.send_refinement()

**Test Cases**:
1. Batch refinement → all frames updated and published
2. GPS coordinates match converted values

---

### `get_changed_frames(flight_id: str, since: datetime) -> List[int]`

**Description**: Gets frames changed since timestamp (for incremental updates).

**Called By**:
- F15 SSE Event Streamer (for reconnection replay)

**Input**:
```python
flight_id: str
since: datetime
```

**Output**: `List[int]` - Frame IDs changed since timestamp

**Test Cases**:
1. Get changes → returns only modified frames
2. No changes → returns empty list

---

### `update_results_after_chunk_merge(flight_id: str, refined_results: List[RefinedFrameResult]) -> bool`

**Description**: Updates results for frames affected by chunk merging into main trajectory.

**Called By**:
- F02.2 Flight Processing Engine (after chunk merge completes)

**Input**:
```python
flight_id: str
refined_results: List[RefinedFrameResult]  # GPS-converted results for merged frames
```

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

**Important**: F14 does NOT call F10, F13, or F11. F02.2 coordinates the chunk merge workflow:
1. F11 returns merge result to F02.2 (direct call return, not event)
2. F02.2 gets updated poses from F10.get_trajectory(flight_id) (ENU coordinates)
3. F02.2 converts ENU to GPS via F13.enu_to_gps(flight_id, enu_tuple)
4. F02.2 constructs RefinedFrameResult objects
5. F02.2 calls F14.update_results_after_chunk_merge() with GPS-converted results

**Processing Flow**:
1. For each refined_result in refined_results:
   - Extract frame_id, gps_center, confidence from RefinedFrameResult
   - Update result with refined=True via F03 Flight Database (part of transaction)
   - Update waypoint via F03 Flight Database (part of transaction)
   - Call F15 SSE Event Streamer.send_refinement()

**Test Cases**:
1. **Chunk merge updates**: All merged frames updated and published
2. **GPS accuracy**: Updated GPS matches optimized poses

## Integration Tests

### Test 1: Per-Frame Processing
1. Process frame 237
2. update_frame_result() → stores result
3. Verify publish_waypoint_update() called (F03.update_waypoint())
4. Verify F15 SSE event sent

### Test 2: Batch Refinement
1. Process 100 frames
2. Factor graph refines frames 10-50
3. mark_refined([10-50]) → updates all
4. Verify Flight Database updated (F03.batch_update_waypoints())
5. Verify SSE refinement events sent

### Test 3: Incremental Updates
1. Process frames 1-100
2. Client disconnects at frame 50
3. Client reconnects
4. get_changed_frames(since=frame_50_time)
5. Client receives frames 51-100

## Non-Functional Requirements

### Performance
- **update_frame_result**: < 50ms
- **publish_waypoint_update**: < 100ms (non-blocking)
- **get_flight_results**: < 200ms for 2000 frames

### Reliability
- Result persistence survives crashes
- Guaranteed at-least-once delivery to Flight Database
- Idempotent updates

## Dependencies

### Internal Components
- **F03 Flight Database**: Must support transactional updates.
- **F15 SSE Event Streamer**: For real-time result streaming.

**Note**: F14 does NOT depend on F10, F13, or F11. The caller (F02.2) coordinates with those components and provides GPS-converted results to F14. This ensures unidirectional data flow and eliminates circular dependencies.

### External Dependencies
- None

## Data Models

### FrameResult
```python
class ObjectLocation(BaseModel):
    object_id: str
    pixel: Tuple[float, float]
    gps: GPSPoint
    class_name: str
    confidence: float

class FrameResult(BaseModel):
    frame_id: int
    gps_center: GPSPoint
    altitude: float
    heading: float
    confidence: float
    timestamp: datetime
    refined: bool
    objects: List[ObjectLocation]
    updated_at: datetime
```

### RefinedFrameResult
```python
class RefinedFrameResult(BaseModel):
    """
    GPS-converted frame result provided by F02.2 after coordinate transformation.
    F02.2 converts ENU poses from F10 to GPS using F13 before passing to F14.
    """
    frame_id: int
    gps_center: GPSPoint  # GPS coordinates (converted from ENU by F02.2)
    confidence: float
    heading: Optional[float] = None  # Updated heading if available
```

### FlightResults
```python
class FlightStatistics(BaseModel):
    total_frames: int
    processed_frames: int
    refined_frames: int
    mean_confidence: float
    processing_time: float

class FlightResults(BaseModel):
    flight_id: str
    frames: List[FrameResult]
    statistics: FlightStatistics
```
