"""C2.5 ``ReRankStrategy`` Protocol (AZ-342). PEP 544 ``typing.Protocol`` with ``runtime_checkable=True``; a single ``rerank`` method that consumes a C2 :class:`VprResult` and produces a :class:`RerankResult` ranked by single-pair LightGlue inlier count. Concrete impl — :class:`InlierCountReRanker` (AZ-343) — lives in a sibling module and is imported lazily by :mod:`gps_denied_onboard.runtime_root.rerank_factory`. The contract at ``_docs/02_document/contracts/c2_5_rerank/rerank_strategy_protocol.md`` v1.0.0 is the authoritative shape; this module mirrors it 1:1. """ from __future__ import annotations from typing import TYPE_CHECKING, Protocol, runtime_checkable if TYPE_CHECKING: from gps_denied_onboard._types.calibration import CameraCalibration from gps_denied_onboard._types.nav import NavCameraFrame from gps_denied_onboard._types.rerank import RerankResult from gps_denied_onboard._types.vpr import VprResult __all__ = ["ReRankStrategy"] @runtime_checkable class ReRankStrategy(Protocol): """Single-camera re-rank strategy. Stateless per-frame; the only persistent state is the constructor-injected :class:`gps_denied_onboard.helpers.lightglue_runtime.LightGlueRuntime` helper handle and the :class:`TileStore` Public API reference. Invariants (see ``rerank_strategy_protocol.md`` v1.0.0): - **INV-1 single-threaded** — each instance is bound to one ingest thread; the shared ``LightGlueRuntime`` requires serial access. Concurrent :meth:`rerank` calls on a single instance race the GPU stream. - **INV-2 stateless per-frame** — same inputs → same surviving candidates in same order. - **INV-3 top-N descending by inlier_count** — ties broken deterministically by ``descriptor_distance`` ascending (the C2-stage value carried forward). - **INV-4 candidates length bounded** — ``0 < len <= n`` when returned (zero raises :class:`RerankAllCandidatesFailedError`); never exceeds ``n``; never exceeds ``len(vpr_result.candidates)``. - **INV-5 descriptor_distance carried forward unchanged** — the C2-stage value is preserved on every survivor for FDR provenance. - **INV-6 tile_pixels_handle is a reference, NOT a copy** — ``RerankCandidate.tile_pixels_handle`` is the same handle returned by ``TileStore.read_tile_pixels`` (page-cache backed). - **INV-7 deterministic per tuple** — same ``(frame, vpr_result, corpus, helper)`` → bit-identical :class:`RerankResult`. - **INV-8 drop-and-continue** — a per-candidate exception NEVER propagates out of :meth:`rerank` unless EVERY candidate fails. C3 relies on this partial-input tolerance. Error envelope: only :class:`RerankAllCandidatesFailedError` escapes :meth:`rerank`; per-candidate :class:`RerankBackboneError` / ``TileFetchError`` from C6 are caught inside the loop and turned into dropped candidates + ERROR logs + per-occurrence FDR records. """ def rerank( self, frame: "NavCameraFrame", vpr_result: "VprResult", n: int, calibration: "CameraCalibration", ) -> "RerankResult": """Re-rank the top-K candidates down to top-N by inlier count. For each ``candidate`` in ``vpr_result.candidates``: 1. Fetch tile pixels via ``TileStore.read_tile_pixels(candidate.tile_id)``. 2. Run a single-pair LightGlue forward via the shared :class:`LightGlueRuntime` (frame ↔ tile). 3. Record the inlier count. Sort candidates descending by inlier count; return the top-N as a :class:`RerankResult`. Drop-and-continue semantics apply per INV-8. Raises: RerankAllCandidatesFailedError: zero survivors after the per-candidate loop. """ ...