# Recommended-Action Policy + Pan Plan Emission

**Task**: AZ-671_semantic_analyzer_action_policy
**Name**: Tier2Evidence action policy + pan-plan emission for footpath-follow
**Description**: At intersections, recommend `PanFollowFootpath | HoldEndpoint | PanBroad | ReturnToZoomOut` based on the primitive graph + freshness + concealment scores + Tier2Evidence shape. Emit a pan plan (sequence of pan goals) when `PanFollowFootpath` is chosen.
**Complexity**: 3 points
**Dependencies**: AZ-640_initial_structure, AZ-669_semantic_analyzer_primitive_graph, AZ-670_semantic_analyzer_roi_cnn
**Component**: semantic_analyzer
**Tracker**: AZ-671
**Epic**: AZ-630

## Problem

Once the primitive graph + freshness + concealment scores are computed, the policy must choose what the gimbal does next. At intersections, the freshest / most-promising branch is recommended for `gimbal_controller` to pan toward; an explicit `pan plan` (sequence of pan goals with timing) is emitted that keeps the path centered while the UAV moves.

## Outcome

- `ActionPolicy::recommend(graph, freshness, concealment, current_roi) -> Tier2Evidence` returns the typed evidence with `recommended_next_action`.
- For `PanFollowFootpath`, the evidence carries an attached `PanPlan` (sequence of `(yaw, pitch, zoom, at_ts)` goals) consumed by `gimbal_controller` (task 16).
- `HoldEndpoint`, `PanBroad`, `ReturnToZoomOut` are returned without a pan plan.
- For graph-invalid (disconnected) cases, returns `recommended_next_action: ReturnToZoomOut` + `path_freshness: undefined`.

## Scope

### Included
- Policy rule table (graph-shape × scores × current ROI → action).
- Pan plan generator (footpath traversal sequence).
- Tier2Evidence type assembly.

### Excluded
- Plan execution (`gimbal_controller` task 16).
- VLM gating (lives in `scan_controller`).

## Acceptance Criteria

**AC-1: Single fresh footpath → PanFollowFootpath**
Given a graph with one path node, freshness > 0.7, no endpoint nodes
When `recommend(...)` runs
Then it returns `Tier2Evidence { recommended_next_action: PanFollowFootpath, pan_plan: Some(...) }`.

**AC-2: Branched intersection picks freshest branch**
Given a graph with three path nodes meeting at an intersection, freshness `[0.3, 0.9, 0.5]`
When `recommend(...)` runs
Then the emitted pan plan's first non-trivial pan goal lies along the branch with freshness 0.9.

**AC-3: High-concealment endpoint → HoldEndpoint**
Given a graph with one endpoint node, concealment > 0.8
When `recommend(...)` runs
Then it returns `Tier2Evidence { recommended_next_action: HoldEndpoint, pan_plan: None }`.

**AC-4: Disconnected graph → ReturnToZoomOut**
Given a graph marked invalid (disconnected paths)
When `recommend(...)` runs
Then it returns `Tier2Evidence { recommended_next_action: ReturnToZoomOut, path_freshness: undefined, pan_plan: None }`.

## Non-Functional Requirements

**Performance**
- Policy + pan-plan generation: ≤20 ms p99 (well within the ≤200 ms Tier 2 budget).

## Contract

- Canonical typed model: `data_model.md §Tier2Evidence`, `§PanPlan`.

## Runtime Completeness

- **Named capability**: Tier-2 action policy + pan-plan emission.
- **Production code that must exist**: real policy rules; real pan-plan generator.
- **Unacceptable substitutes**: a "always return PanFollowFootpath" placeholder is unacceptable in production.
