Per-Feature Post-Mortem (NavFeatureSummary)
Overview
NavFeatureSummary is the small frozen
dataclass that records one entry per emitted
NavFeature in the per-image
feature_inventory list. The summary
carries enough information about each feature for the curator to write a post-mortem entry
into the per-image JSON sidecar — the per-feature identifier, its type, its source model,
the gate decision (kept vs. dropped, with a stable reason), and its bounding box. It does
not carry the heavy bits of a full
NavFeature (templates, polylines, covariance) because those
would bloat the metadata.
Theory
The summary is a deliberately narrow record. Heavy-bit features (BODY_DISC templates, LIMB_ARC polylines) routinely run to tens of kilobytes per feature; surfacing every byte of every feature in the per-image JSON would balloon the sidecar to multi-megabyte sizes without operationally useful content. The summary keeps the post-mortem structure (one entry per emitted feature) without the bulk.
The gated flag and the
gate_reason together
describe the per-feature reliability-gate decision: gated=False means the feature
reached the technique-feasibility loop; gated=True means the gate dropped it before
any technique saw it. The reason string is stable across images so reviewer tooling can
correlate gate refusals across an entire imaging campaign.
Restrictions and assumptions
Every field is required at construction time. The dataclass enforces invariants in
__post_init__: feature id and source model must be non-empty strings; reliability must lie in \([0, 1]\); the bounding box must be a length-4 tuple of ints.The gate-reason field is required non-empty when
gatedisTrue.The dataclass is frozen.
Sources of uncertainty
The summary reports no uncertainty. The reliability score is the per-feature value the
producing NavModel reported; the gate decision is
deterministic given the per-image context.
Configuration
The dataclass carries no YAML configuration of its own. The per-feature reliability
floor that drives the gate decision lives on the
FeatureReliabilityGate (per-type floor) rather than on
this summary dataclass.
Implementation
Source file: src/nav/nav_orchestrator/feature_summary.py —
NavFeatureSummary.
Public class NavFeatureSummary, frozen
dataclass. Public fields (autodocumented at
nav.nav_orchestrator):
feature_id— unique identifier matching the producingfeature_id. Non-empty string.feature_type— one of theNavFeatureTypeenum values.source_model— name of the producingNavModel('stars','body:DIONE','rings:SATURN'). Non-empty string.reliability— per-feature reliability score in \([0, 1]\).gated— bool.Truewhen the reliability gate dropped this feature before any technique saw it.gate_reason— str orNone. Stable English reason whengatedisTrue;Noneotherwise.bbox_extfov_vu— half-open(v_min, u_min, v_max, u_max)bounding box in extfov coordinates; 4-tuple of ints.
The dataclass enforces invariants in __post_init__: every field must have the
declared type; reliability must lie in \([0, 1]\); gate_reason must be a
non-empty string when gated is True; bbox_extfov_vu must be a length-4 tuple
of real int (numpy int / bool are rejected).
The orchestrator’s
navigate() builds the per-image
inventory by iterating over the kept and gated cohorts and constructing one
NavFeatureSummary per feature via the
private _summary_from_feature helper, which projects the heavy
NavFeature down to the summary’s narrow shape.
Examples
Kept feature on body_partial_overflow. The body model emits a LIMB_ARC for Rhea with
reliability 0.82 and bounding box (180, 700, 540, 1024). The orchestrator’s gate
keeps it, so the inventory entry is:
NavFeatureSummary(
feature_id='limb_arc:RHEA',
feature_type=NavFeatureType.LIMB_ARC,
source_model='body:RHEA',
reliability=0.82,
gated=False,
gate_reason=None,
bbox_extfov_vu=(180, 700, 540, 1024),
)
Gated feature. The body model emits a LIMB_ARC for a fully-lit body whose
reliability score falls below the per-type floor (the textbook body_full_fov case).
The gate drops it:
NavFeatureSummary(
feature_id='limb_arc:DIONE',
feature_type=NavFeatureType.LIMB_ARC,
source_model='body:DIONE',
reliability=0.14,
gated=True,
gate_reason='reliability_below_floor',
bbox_extfov_vu=(364, 364, 644, 644),
)
The reason string is stable across images: a campaign-level reviewer who counts
gate_reason='reliability_below_floor' occurrences across 1,000 body_full_fov-class
images learns directly how often the LIMB_ARC reliability formula’s incidence-factor
penalty saturates on full-disc bodies.
Per-image inventory size. A multi-body Cassini fly-by image with 3 bodies plus 50
predictable stars produces an inventory of 50 STAR plus up to 12 body-derived features
(LIMB_ARC + BODY_DISC + TERMINATOR_ARC per body) — about 62 entries totalling ~6 KB of
JSON. The same scene’s heavy-bit
NavFeature objects total tens of megabytes; the summary’s
narrow shape is what makes the per-image sidecar tractable to read in a reviewer’s text
editor.