Skip to content

Demo#1

Merged
hahuang65 merged 25 commits into
mainfrom
hh/demo
Jun 16, 2026
Merged

Demo#1
hahuang65 merged 25 commits into
mainfrom
hh/demo

Conversation

@hahuang65

Copy link
Copy Markdown
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

hahuang65 added 25 commits June 11, 2026 01:47
- 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.
@hahuang65
hahuang65 merged commit c71fca7 into main Jun 16, 2026
2 checks passed
@hahuang65
hahuang65 deleted the hh/demo branch June 16, 2026 02:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant