Conversation
hahuang65
commented
Jun 16, 2026
Member
- FEATURE: flight model + BDF bitmap renderer + metric cycling demo
- FIX: switch build backend to hatchling to suppress egg-info artifact
- FEATURE: config + FlightAPI protocol + AeroAPI adapter
- feat: adsb.lol adapter + adsbdb route enrichment
- REFACTOR: extract AeroAPI fixture test into dedicated class
- FEATURE: switch AeroAPIAdapter to IATA airport codes
- FEATURE: filter GA aircraft before route enrichment
- REFACTOR: drop AeroAPI adapter
- feat: limit route enrichment to 5 flights
- refactor: extract App class, rename main.py → app.py, use config-driven pause/refresh
- feat: filter out grounded flights from display
- fix: check aircraft type against commercial whitelist in _is_commercial
- refactor: let adsbdb decide what's a real flight
- refactor: enrich routes until enough have routes, not a flat cap
- REFACTOR: extract _fetch_route and Route NamedTuple
- REFACTOR: add units to metric labels and shorten them
- FEATURE: add route plausibility checks with bearing alignment and cross-track distance
- CHORE: add structured logging and debug targets
- REFACTOR: extract display formatting from Flight model
- REFACTOR: extract geospatial functions into geo module
- REFACTOR: extract RequestsAPI from fetcher into http module
- FEATURE: add route cache for adsbdb lookups with 15-minute TTL
- REFACTOR: extract _parse_airport helper from _fetch_route
- FEATURE: add flight history with stale-flight refresh and LOADING state
- chore: add DEBUG logging for route plausibility checks
- Flight dataclass with callsign-derived airline/flight_number and display helpers (flight_label, route_label, metrics_label) - FlightBuffer ring buffer with callsign dedup and maxlen - BDF bitmap font (5x7) renderer via rgbmatrix graphics.DrawText - 4-page metric cycling (altitude, speed, vertical rate, track) - Main demo loop cycling through all pages - 19 tests passing, ruff clean
Setuptools was generating `src/jetset.egg-info/` on every `uv sync`. Replaced setuptools with hatchling build backend, removing the egg-info noise from the source tree while keeping the package installable and its entry points functional.
Add AppConfig dataclass with YAML loading, FlightAPI protocol for swappable data sources, and the first concrete AeroAPI adapter. - AppConfig: frozen dataclass, nested YAML flattened to underscore fields (home_lat, home_lon, range, cycle, refresh, api_source) - FlightAPI: Protocol with nearby_flights(lat, lon, range, raw=False) - AeroAPIAdapter: fetches via FlightAware AeroAPI /flights/search, maps ident/origin/destination/aircraft_type/altitude/groundspeed/ heading to Flight model; altitude converted from hundreds of ft - bounding_box utility (~1deg ≈ 111km) - Error handling: catches RequestException and ValueError - scripts/save_fixtures.py: generic FixtureProvider abstraction, env var derived from adapter class name - python-dotenv for .env loading, .gitignore excludes .env - Tests: real fixture + hand-written example, 34 passing - Config default api_source changed to 'aeroapi' - Removed opensky-api dependency and type stubs - Updated CONTEXT.md, PRD, tasks.md for AeroAPI
Replace AeroAPI as the primary data source with the free adsb.lol
API for real-time aircraft data, paired with adsbdb.com for route
enrichment (origin/destination by callsign lookup).
Changes:
- AdsbLolAdapter: fetches nearby flights via /v2/point/{lat}/{lon}/{dist}
with km→nm conversion, enriches routes via adsbdb.com/callsign/{cs}
- AdsbdbUnknownCallsign handling: guard for 'unknown callsign' string
response from adsbdb (non-dict response)
- Route enrichment: adsb.lol aircraft dicts mutated in-place with
origin/destination before json_to_flight conversion
- Config default api_source → adsblol, home_lon → -95.3416 (IAH fix)
- save_fixtures.py: keyless adapter support (try instantiation without
API key if env var not set), AdsbLolAdapter added to FIXTURES
- AeroAPIAdapter: handle null origin/destination in json_to_flight
- Tests: AdsbLolAdapter unit tests (json_to_flight, enrich_routes,
nearby_flights, raw mode), FixtureProvider save test, relaxed
assertion in AeroAPI test (origin/dest can be None)
- Fixtures: adsblol_response.json (37 live flights), aeroapi_response.json
regenerated with fresh data
Move test_parses_all_fixture_flights from TestAeroAPIFlightToFlight into new TestAeroAPIFixture class, matching TestAdsbLolFixture convention. Also adds origin/destination type assertions to align with the AdsbLol fixture test.
Change origin/destination field from .code (ICAO) to .code_iata (IATA) for consistency with the adsb.lol adapter's route enrichment.
Add _is_commercial static method to AdsbLolAdapter that filters out non-commercial aircraft (tail numbers, missing callsigns) before calling adsbdb for route enrichment. Cuts adsbdb calls by ~45% (from 37 to ~20 on fixture data). nearby_flights flow: fetch → filter commercial → enrich → raw/convert. Raw mode returns filtered + enriched dicts.
Remove AeroAPIAdapter, bounding_box utility, AeroAPI tests, aeroapi_response.json fixture, and python-dotenv dependency. AdsbLolAdapter is now the sole data source.
Truncate commercial aircraft list to 5 before calling adsbdb, matching the FlightBuffer display capacity.
Add _is_airborne() helper that rejects flights with alt_baro of None, "ground", or 0. Chain it with _is_commercial() before selecting the top 5 flights to display.
A 3-letter callsign pattern alone is insufficient — GA aircraft like TRF558 (P28A Piper) also match. Now if the aircraft has a known type code (t), it must be in COMMERCIAL_AIRCRAFT_TYPES to pass the commercial filter. Unknown types are let through as a safety net.
Drop _is_commercial() and COMMERCIAL_AIRCRAFT_TYPES whitelist entirely. Instead: filter airborne -> enrich routes (up to 20 callsigns) -> take first 5 that actually have origin + destination. Adsdb knows what's commercial better than we ever will.
_enrich_routes now iterates flight_data in order, enriches each unique callsign, and stops once max_flights successfully resolve (origin + destination found). Only successful results get origin/destination set, so nearby_flights can just filter on those fields.
- Route NamedTuple replaces inline dict for route data - _fetch_route(callsign) isolates single adsbdb call with error handling - _enrich_routes returns enriched list directly (no two-phase mutate+filter) - Duplicate callsigns now also added to enriched list (bug fix) - nearby_flights simplified: _enrich_routes returns ready-to-use list
- ALT/Speed/Vertical rate pages now show ft/kt/ft/m units - Level flight (vertical_rate=0) shows LVL - All metrics removed the name label to save space
…ss-track distance - Add Position dataclass (lat/lon) and refactor Airport to hold Position - Add FlightRoute.bearing — great-circle initial bearing (0-360°) - Add FlightRoute.distance — haversine distance in NM - Add FlightRoute.cross_track_distance(position) — perpendicular NM off route - Add FlightRoute.plausible(track, position, max_xtd) — checks bearing alignment (≤60°) and cross-track distance (≤max_xtd NM) - Update _enrich_routes to call plausible() with aircraft track and position - Thread user's configured range (km) from nearby_flights through as max_xtd (NM) for the cross-track threshold
- Replace print debugging with structured logging (JETSET_DEBUG env) - Log Flight and FlightRoute instantiation at DEBUG level - Add make debug target - Suppress duplicate RGBME output from root logger - Bump default range to 200 km
Move flight_label, aircraft_label, route_label, and metrics_label out of the Flight dataclass into standalone functions in a new display module. Flight is now a pure data container. Why: display-formatting logic is a rendering concern, not a model concern. Keeping it separate makes the data model easier to reason about and centralises panel-specific formatting for future slices (e.g. NO DATA text for Slice 4).
Move Position dataclass, EARTH_RADIUS constant, and great-circle bearing/distance/cross-track calculations from FlightRoute into a standalone geo module. FlightRoute keeps thin delegate methods so callers are unaffected. Why: separates geometry concerns from the data model, makes raw geo functions testable in isolation, and avoids creating dummy FlightRoute instances just to compute distances (as cross_track_ _distance previously did).
Move the RequestsAPI session wrapper into its own http module. No logic changes — fetcher imports it from jetset.http instead. Why: the HTTP utility is a general concern reused by adapters. Extracting it makes fetcher.py focused on flight-specific adapter logic and keeps reusable infrastructure separate.
Add RouteCache class that stores FlightRoute results from adsbdb, keyed by callsign, with time-based expiry (default 900s). Cache is stored on the AdsbLolAdapter instance so it persists across fetch cycles. Rewrite _enrich_routes to: - Check the cache before calling _fetch_route - Cache newly-fetched plausible routes - Return a new enriched list without mutating input dicts - Remove function-local route_map/seen variables Why: halves adsbdb API calls on subsequent fetch cycles for the same flights (common when flights are in range for many minutes). No-mutation makes the enrichment flow easier to reason about.
Add _parse_airport static method to safely parse an Airport from the adsbdb response dict, returning None for missing/incomplete fields. Rewrite _fetch_route to use it, flattening the deep nesting with early returns. Why: eliminates the repeated dotted .get() chains for origin and destination, makes _fetch_route easier to follow, and the helper is independently testable.
Add refresh_flight to FlightAPI protocol and AdsbLolAdapter to fetch current metrics for a known flight while preserving its cached route. Replace stale buffer entries after each fetch cycle via refresh_flight. Add FlightBuffer.replace() to update a flight in-place by callsign. Add loading_label() with animated cycling text (LOADING → LOADING. → LOADING.. → LOADING...) for the empty buffer state, rendered via render_loading() / App._render_loading(). Extract _parse_airport helper from _fetch_route to DRY up airport parsing from adsbdb response dicts.
Log bearing diff and cross-track distance in _enrich_route when DEBUG is enabled, showing route endpoints, track, bearing, diff, cross-track NM, and ACCEPTED/REJECTED outcome. Helps diagnose false-positive route enrichments.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.