Extract PointsPath into AwardProvider interface (work-z6zi)#8
Merged
Conversation
Lays the groundwork for plugging in seats.aero (work-2eoa) as a second award source. Today PP is the only provider; the Protocol + AwardFlight shape are designed around the identity fields the matcher already keys on (flight#+date and route+time) plus the per-provider metadata the renderer already consumes (program, banks, miles_to_cash_ratio). No behavior change in this commit — base.py is unused until the PointsPath adapter lands in the next commit.
Wraps the existing PPClient + pricing-info into the AwardProvider Protocol. Conversion from PP's OutboundFlight + PerCabinMilesPricing into the neutral AwardFlight/CabinAward types lives here so future providers (seats.aero) can build the same neutral output from their own raw shapes. Cabin-award translation logic is duplicated from pp.match._cabin_awards to keep the adapter self-contained; the duplicate goes away in the next commit when match.py is refactored to consume AwardFlight directly.
…egistry The matcher's signature becomes provider-blind: `join(search, awards)` where `awards` is a flat `list[AwardFlight]` from any number of providers. The existing (flight#, date) and (route, time) keys stay — they were already provider-agnostic; only the input shape changed. `pp/cli.py:run_pp_for_search` now drives the providers/registry fan-out rather than instantiating PPClient directly. Today the registry yields PointsPath only; seats.aero (work-2eoa) plugs in by adding one entry and its own `provider.py`/`is_configured()` pair — no caller-side change. Drops the unused `use_inbound` parameter from join(): no production caller ever set is_round_trip_return=True, so `inboundFlights` was never populated. The future round-trip-inbound case can be re-added at the provider layer (distinct `search_leg_inbound` method or similar) if it ever lands. Renderer reads provider/program/funding_banks directly off AwardFlight; the PP-only table picks up a `source` column so seats.aero's rows are distinguishable when it joins.
Stub-provider tests pin the contract: per-leg concatenation across enabled providers; per-provider exceptions are isolated (one failure doesn't sink the others); empty provider list returns empty list.
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.
Summary
Foundation for
seats.aero(work-2eoa) and any future award source. The matcher is now provider-blind:match.py:join(search, awards)takes a flatlist[AwardFlight]from any provider via the new registry. PointsPath is just the first one.Shipped as four commits — review them sequentially for the cleanest mental load:
providers/base.py. Zero behavior change.PPClientinto the new Protocol. Conversion from PP'sOutboundFlight+PerCabinMilesPricinginto the neutralAwardFlight/CabinAwardtypes lives in the adapter.join()signature becomes(search, list[AwardFlight]).pp/cli.py:run_pp_for_searchnow drives the registry fan-out instead of instantiatingPPClientdirectly. Renderer readsprovider/program/funding_banksdirectly offAwardFlight; PP-only table gets asourcecolumn.What's deferred
pp/{auth,client,models,match}.py→providers/pointspath/): pure rename + deprecation shim. Mechanical follow-up — no capability change.flight auth list+ per-provider auth subcommands: scoped to when ≥2 providers exist.seats.aero: work-2eoa, now unblocked by this PR.Notable deletions
use_inboundparameter onmatch.join()— no production caller ever setis_round_trip_return=True, soinboundFlightswas never populated. Reintroducible at the provider layer if a round-trip-inbound case ever lands.Verification
make checkgreen: 120 tests passing (was 115: +6 registry tests, +/- on match tests).tests/test_registry.py.tests/pp/test_match.py(now constructsAwardFlightdirectly instead ofAirlineSearchResponse).Test plan
flight search EWR MIA --dep 2026-08-15→ gflight + PP overlay still renders.flight search JFK LHR --dep 2026-08-15 --backend matrix→ matrix + PP overlay still renders.flight search EWR MIA --dep 2026-08-15 --pp-only→ PP-only table renders with the newsourcecolumn.flight search EWR MIA --dep 2026-08-15 --no-pp→ cash only, no PP.Closes work-z6zi.