# Primitive Graph Builder + Path Freshness Scoring

**Task**: AZ-669_semantic_analyzer_primitive_graph
**Name**: Primitive graph from Tier-1 detections + path-freshness scoring
**Description**: Build a small ROI-scoped primitive graph from Tier-1 detections (path nodes, endpoint nodes, context nodes). Score path freshness using texture, edge clarity, undisturbed-surroundings cues.
**Complexity**: 5 points
**Dependencies**: AZ-640_initial_structure, AZ-660_detection_client_grpc_stream, AZ-661_detection_client_schema_and_health
**Component**: semantic_analyzer
**Tracker**: AZ-669
**Epic**: AZ-630

## Problem

Tier 2 reasons over zoom-in crops using a primitive graph built from Tier-1 detections. The graph captures footpaths (path nodes), branch piles / dark entrances / dugouts (endpoint nodes), and trees / tree-blocks (context nodes). Path-freshness scoring combines surface texture, edge clarity, and undisturbed-surroundings cues into a single freshness score consumed by the recommended-action policy.

## Outcome

- `PrimitiveGraph::build(roi, detections) -> Graph` builds the graph from Tier-1 detections inside the ROI.
- `FreshnessScorer::score(graph, frame_crop) -> PathFreshnessScore` returns a normalized 0–1 score per path node.
- Graph validation: disconnected paths trigger an explicit warning (consumed by task 32).
- Health surface: `graphs_built_total`, `freshness_score_p50/p99`, `disconnected_graphs_total`.

## Scope

### Included
- Graph data structures (path / endpoint / context node types).
- Detection-to-node mapping (per-class).
- Freshness scoring (computer-vision-style: edge density, texture variance, surrounding undisturbed area).
- Graph validation.

### Excluded
- ROI CNN inference (task 31).
- Recommended-action policy (task 32).
- VLM (separate component).

## Acceptance Criteria

**AC-1: Graph contains all relevant detections**
Given a `DetectionBatch` with 3 footpath bboxes + 2 branch-pile bboxes + 5 tree bboxes inside the ROI
When `build(roi, batch)` runs
Then the graph contains 3 path nodes + 2 endpoint nodes + 5 context nodes.

**AC-2: Freshness score is bounded**
Given any valid graph + frame crop
When `score(graph, crop)` runs
Then every emitted freshness score is in `[0.0, 1.0]`.

**AC-3: Disconnected graph is flagged**
Given a graph with two unconnected path components
When validation runs
Then `disconnected_graphs_total` increments by 1 and the graph is marked invalid.

## Non-Functional Requirements

**Performance**
- Graph build: ≤30 ms per ROI on Jetson Orin Nano.
- Freshness scoring: ≤50 ms per ROI.

## Runtime Completeness

- **Named capability**: primitive graph construction + path-freshness scoring — production reasoning path.
- **Production code that must exist**: real graph construction; real freshness scorer.
- **Allowed external stubs**: `opencv` for texture/edge feature extraction.
- **Unacceptable substitutes**: a constant-score scorer in production is unacceptable.
