[AZ-489] C12 FlightsApiClient + offline JSON loader + bbox helper

ADR-010 primary cold-start path now has a real source for the cache bbox
and the takeoff origin. Single concrete strategy (`HttpxFlightsApiClient`)
behind a `@runtime_checkable` Protocol; offline JSON fallback (`load_flight_file`)
shares the same DTO shape per FAC-INV-1.

* `flights_api/interface.py` — `FlightsApiClient` Protocol + `FlightDto`
  + `WaypointDto` + `WaypointObjective` / `WaypointSource` enums (plain
  frozen-slotted dataclasses, matching project's LatLonAlt / PoseEstimate
  pattern).
* `flights_api/errors.py` — 8-class hierarchy under `FlightsApiError`.
* `flights_api/_parser.py` — shared JSON validator: range checks, lat/lon
  bounds, contiguous ordinals, finite floats, enum membership.
* `flights_api/bbox.py` — `bbox_from_waypoints` envelopes lat/lon and
  inflates by a horizontal-distance buffer via WgsConverter ENU
  round-trip (NOT degree-space); `takeoff_origin_from_flight` passes
  waypoints[0] through unrounded.
* `flights_api/file_loader.py` — orjson-backed offline loader.
* `flights_api/httpx_client.py` — concrete client with ONE retry on
  transient 5xx + connect errors; token redaction at every log site;
  test-injectable transport + sleep.
* `runtime_root/c12_factory.py` — `build_flights_api_client(config)`;
  re-exported from `runtime_root/__init__.py`. OperatorToolServices
  aggregate intentionally deferred to AZ-328 per scope discipline.
* `pyproject.toml` — `httpx>=0.28,<1.0` added (chosen over `requests`
  for native `MockTransport` testing).

Tests: 28 cases across AC-1..AC-18 plus extras (malformed JSON,
negative buffer, zero buffer, missing top-level fields, negative
ordinal, empty-flight takeoff). Full repo run: 713 passed, 2 skipped.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-12 01:28:49 +03:00
parent e0be591b06
commit 72a06edab0
15 changed files with 2057 additions and 2 deletions
@@ -24,6 +24,9 @@ from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Any, Final, Literal, get_args
from gps_denied_onboard.config import Config, load_config
from gps_denied_onboard.runtime_root.c12_factory import (
build_flights_api_client,
)
from gps_denied_onboard.runtime_root.fc_factory import (
OutboundThreadAlreadyBoundError,
bind_outbound_emit_thread,
@@ -77,6 +80,7 @@ __all__ = [
"bind_outbound_emit_thread",
"bind_state_ingest_thread",
"build_fc_adapter",
"build_flights_api_client",
"build_gcs_adapter",
"build_pose_estimator",
"build_state_estimator",
@@ -0,0 +1,44 @@
"""Composition-root factory for C12 operator-tooling services (AZ-489).
Currently exposes :func:`build_flights_api_client` — the
:class:`FlightsApiClient` used by C12's pre-flight cache-build workflow
(see AZ-326 / AZ-328 for downstream consumers).
The factory is intentionally tiny: there is only one concrete strategy
(``HttpxFlightsApiClient``) and httpx already defaults to TLS verify ON
and the system trust store, so the factory's job is to assemble the
client without re-implementing those defaults.
The richer ``OperatorToolServices`` dataclass that aggregates this
client with the rest of C12 (``CacheBuildWorkflow``,
``OperatorReLocService``, etc.) is owned by AZ-328 and intentionally
NOT created here per scope discipline.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from gps_denied_onboard.components.c12_operator_tooling.flights_api import (
FlightsApiClient,
HttpxFlightsApiClient,
)
if TYPE_CHECKING:
from gps_denied_onboard.config import Config
__all__ = ["build_flights_api_client"]
def build_flights_api_client(config: Config) -> FlightsApiClient:
"""Return the operator-tier :class:`FlightsApiClient`.
The current implementation is the production
:class:`HttpxFlightsApiClient` with httpx defaults (TLS verify ON,
system trust store). ``config`` is accepted for API parity with the
other ``build_*`` factories; the client itself does not need
composition-time configuration — the operator's base URL and auth
token are resolved per-call by the CLI layer (AZ-326).
"""
_ = config # reserved for future composition-time tuning
return HttpxFlightsApiClient()