Skip to content

Wire PointsPath matched-id join through gflight backend#10

Merged
ak2k merged 1 commit into
mainfrom
worktree-gflight-matched-ids
May 15, 2026
Merged

Wire PointsPath matched-id join through gflight backend#10
ak2k merged 1 commit into
mainfrom
worktree-gflight-matched-ids

Conversation

@ak2k

@ak2k ak2k commented May 15, 2026

Copy link
Copy Markdown
Owner

Summary

PP's enableGoogleFlightMatching=true mode DOES work — earlier "dead end" framing was wrong. With Google's real opaque flight ID (from data[0][17], which fli throws away) and the right hint shape, PP echoes matchedGoogleFlightId back per result. The matcher uses that as an exact-equality primary key, which catches codeshares the (flight#, date) heuristic misses.

Empirical verification (end-to-end live)

fli MIA→LHR 2026-06-30 — 120 flights with captured opaque IDs
hints built — 50 (correct format: IATA-prefixed flight#, human-readable airline, space-separated times)
provider returned 41 awards, ALL 41 with matchedGoogleFlightId populated

matcher: 34/120 cash itineraries have awards attached
  AA38 → 4 awards: American, Etihad, Qantas, Alaska   ← matched-id catches codeshares
  VS118 → 1 award: VirginAtlantic
  BA208 → 2 awards: American, Qantas                  ← matched-id catches codeshares

The matched-ID path surfaces awards from multiple PP programs per cash itinerary — for codeshare flights, flight#+date alone would only have found the marketing carrier.

Discovery path

Captured the PP browser extension's live traffic via Patchright + a snapshot of the user's Chrome profile. 549 events, 87 successful airline-search responses. Settled the format (per docs/memories/pp_matched_id_recipe.md):

  • firstFlightNumber MUST be IATA-prefixed ("VS6", not "6") — bare numbers → 0 flights
  • airline MUST be the human-readable name ("Virgin Atlantic", not "VS") — IATA code → 0 flights
  • startDateTime/endDateTime space-separated ("2026-06-30 18:05"), no T, no TZ
  • flightId = Google's opaque ID, captured from raw API response data[0][17]

Architecture

One PR, layered:

  1. src/flight_cli/_gflight_ids.py — wraps fli's encoder + curl_cffi client, parses the response ourselves to capture data[0][17] (the opaque ID fli drops).
  2. src/flight_cli/_airline_names.py — IATA → human-readable name mapping for PP-supported airlines.
  3. Slice.flight_id — optional, populated by gflight adapter, None for Matrix cash.
  4. AwardFlight.matched_google_flight_id — populated from PP's response when matching was enabled.
  5. AwardProvider.search_leg gained cash_hints kwarg in the Protocol — uniform interface for future providers.
  6. registry.gather_awards plumbs cash_hints_per_leg through to the provider.
  7. match.join — third index keyed on matched_google_flight_id, priority above (flight#, date) and (route, time). Heuristics remain as fallback.
  8. pp/cli.py:run_pp_for_search — auto-builds hints from any SearchResult whose slices carry flight_id (gflight backend).

Renderer untouched. PP response shape unchanged; only the matcher's join logic and provider's request flag differ.

Tests

128 green (was 120, +8). New tests cover:

  • Slice.flight_id population from GFlightWithId
  • cash_hints_from_search_result output shape (matches PP-captured format exactly)
  • Matched-id key fires when present
  • Matched-id bridges flight-number mismatches
  • Dedup across matched-id and heuristic keys
  • Award without matched_google_flight_id still joins via heuristics
  • Cash without flight_id skips matched-id path (Matrix backend compat)

Memory

  • docs/memories/pp_matched_id_recipe.md (new) — full recipe, empirical evidence, counter-evidence vs prior dead-end note.
  • MEMORY.md index updated to point at the new doc.

Test plan

  • flight search EWR MIA --dep 2026-08-15 (plain gflight + PP) — verify award columns populate.
  • flight search JFK LHR --dep 2026-08-15 --backend matrix — matrix path still works (no flight_id, falls back to flight#+date heuristics).
  • flight search JFK LHR --dep 2026-08-15 --backend gflight --json — verify JSON includes flight_id.
  • flight search JFK LHR --dep 2026-08-15 --no-pp — gflight only, no PP overlay.

Empirically settled (and reverses the 'dead end' framing in pp_on_gflight.md):
PP's enableGoogleFlightMatching=true mode DOES work — we just needed the
real Google Flights opaque ID from data[0][17] and the right hint shape.
With that, PP echoes matchedGoogleFlightId back per result, which the
matcher uses as its primary join key — exact string equality, robust to
codeshares where flight#+date fails.

Verified end-to-end against PP Pro: 50 hints in, 41 award matches back,
all 41 with matchedGoogleFlightId populated. AA38 surfaces 4 different
PP programs (American, Etihad, Qantas, Alaska) where flight#+date would
only have caught the marketing carrier.

Architecture changes (one PR, multiple layers):

- src/flight_cli/_gflight_ids.py — wraps fli's encoder + client, parses
  the response ourselves to capture data[0][17] (the opaque ID fli throws
  away).
- src/flight_cli/_airline_names.py — IATA → human-readable name mapping
  for the airlines PP services. Required: the airline-search hint payload
  needs the human-readable name, NOT the IATA code, or PP returns 0 flights.
- Slice.flight_id (optional): populated by the gflight adapter, None for
  Matrix cash itineraries.
- AwardFlight.matched_google_flight_id: populated by PointsPathProvider
  from PP's response when enable_matching was used.
- AwardProvider.search_leg gained cash_hints kwarg (Protocol-level so
  seats.aero and future providers see a uniform interface even if they
  ignore the hints).
- registry.gather_awards accepts cash_hints_per_leg; PointsPathProvider
  consumes it and sets SearchSpec(enable_matching=True, cash_hints=...).
- match.join: third index keyed on matched_google_flight_id with priority
  above the existing (flight#, date) and (route, time) heuristics. The
  heuristics stay as fallback for Matrix cash and gflight-without-IDs.
- pp/cli.py:run_pp_for_search auto-builds hints from any SearchResult
  whose slices carry flight_id (gflight backend).

Render is unchanged. PP's response data is the same shape with or without
matching enabled; only the matcher's behaviour changes.

Tests: 8 new (5 matcher, 3 adapter). All 128 green.

Memory:
- docs/memories/pp_matched_id_recipe.md (new) — full recipe + empirical
  evidence + counter-evidence vs the prior dead-end note.
- MEMORY.md index updated to point at the new doc as the corrected story.
@ak2k ak2k merged commit aab4d66 into main May 15, 2026
2 checks passed
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