mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-23 03:41:12 +00:00
[AZ-348] C3.5 ConditionalRefiner Protocol + factory + PassthroughRefiner
Defines the public `ConditionalRefiner` Protocol (PEP 544 @runtime_checkable, two methods: `refine_if_needed` + `was_invoked`), extends `MatchResult` in-place with two default-valued refinement fields (`refinement_label`, `refinement_added_latency_ms`), defines the `RefinerError` family (`RefinerBackboneError`, `RefinerConfigError`), and ships the trivial `PassthroughRefiner` reference impl. Both refiner strategies are linked unconditionally — no `BUILD_REFINER_*` flag (NOT ADR-002 territory). Runtime selection only per ADR-001. `PassthroughRefiner` returns the input `MatchResult` by reference (bit-identical correspondences per contract INV-5) and always reports `was_invoked() is False`. Documentation: renames `module-layout.md` `c3_5_adhop` Public API symbol from `AdHoPRefinementStrategy` to `ConditionalRefiner` (AC-14) so the doc agrees with `description.md` and the contract. AC-9 (single-thread binding) deferred to AZ-270 runtime-root composition, mirroring AZ-336 / AZ-342 / AZ-344 Risk-4 precedent. AC-7 for the `"adhop"` strategy stops at `ModuleNotFoundError` because the AdHoP backbone is owned by AZ-349. All other ACs + NFRs covered by 36 new conformance tests. Architectural note: `PassthroughRefiner.inference_runtime` is typed as `object` because the L3→L3 import ban (`test_az270_compose_root`) forbids c3_5_adhop from importing c7_inference; the runtime-root factory narrows the type at construction time. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
"""C3.5 ``ConditionalRefiner`` config block (AZ-348).
|
||||
|
||||
Registered into ``config.components['c3_5_adhop']`` by the
|
||||
package ``__init__.py``. The composition-root factory
|
||||
:func:`gps_denied_onboard.runtime_root.refiner_factory.build_refiner_strategy`
|
||||
reads this block to select the strategy and configure thresholds.
|
||||
|
||||
``strategy`` selects one of the two concrete refiners
|
||||
(``adhop`` — production-default; ``passthrough`` — baseline /
|
||||
smoke / IT-12 comparison). Both modules are linked
|
||||
unconditionally: there is NO ``BUILD_REFINER_*`` flag (NOT ADR-002
|
||||
territory). Runtime selection only.
|
||||
|
||||
``residual_threshold_px`` is the conditional-gate threshold: a
|
||||
:class:`MatchResult` whose ``reprojection_residual_px <=
|
||||
threshold`` is passed through unchanged; ``>`` invokes the
|
||||
strategy's refinement procedure. Default 2.5 px (the AC-NEW-5 /
|
||||
R10 tunable from operator tooling).
|
||||
|
||||
``invocation_rate_warn_threshold`` is the rolling-60 s
|
||||
invocation-rate ceiling above which a WARN log fires
|
||||
(C3.5-IT-03 / NFT-PERF-01). Must be in ``(0, 1)``; default 0.25.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Final
|
||||
|
||||
from gps_denied_onboard.config.schema import ConfigError
|
||||
|
||||
__all__ = [
|
||||
"C3_5RefinerConfig",
|
||||
"KNOWN_STRATEGIES",
|
||||
]
|
||||
|
||||
|
||||
KNOWN_STRATEGIES: Final[frozenset[str]] = frozenset({"adhop", "passthrough"})
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class C3_5RefinerConfig:
|
||||
"""Per-component config for C3.5 conditional refiner."""
|
||||
|
||||
strategy: str = "adhop"
|
||||
residual_threshold_px: float = 2.5
|
||||
invocation_rate_warn_threshold: float = 0.25
|
||||
|
||||
def __post_init__(self) -> None:
|
||||
if self.strategy not in KNOWN_STRATEGIES:
|
||||
raise ConfigError(
|
||||
f"C3_5RefinerConfig.strategy={self.strategy!r} not in "
|
||||
f"{sorted(KNOWN_STRATEGIES)}"
|
||||
)
|
||||
if self.residual_threshold_px <= 0.0:
|
||||
raise ConfigError(
|
||||
"C3_5RefinerConfig.residual_threshold_px must be > 0; "
|
||||
f"got {self.residual_threshold_px}"
|
||||
)
|
||||
if not (0.0 < self.invocation_rate_warn_threshold < 1.0):
|
||||
raise ConfigError(
|
||||
"C3_5RefinerConfig.invocation_rate_warn_threshold must be in "
|
||||
f"(0, 1); got {self.invocation_rate_warn_threshold}"
|
||||
)
|
||||
Reference in New Issue
Block a user