Airbeat 0.1.1
Released 2026-10-05 · tag 0.1.1 · all releases
This release adds rail station data and estimated rail fares, hardens the price cache, and makes trip ranking more faithful: fuller route frontiers, a wider stopover window, request weights that steer hub discovery, and honest handling of trips priced in more than one currency.
Highlights
- Rail stations and estimated rail fares. Each rail place code now maps to one main station (for example
PAR → Paris Gare de Lyon). Coordinates come from Wikidata, which is CC0. A timetabled train with no published fare becomes an estimated rail leg, priced and timed from the distance between the two stations. Production has no live timetable source yet (see Known limitations).
- More accurate self-arranged gaps. Where the traveller has to bridge part of a route themselves, each end of that gap is now measured from where they actually are. After a train that’s the station; after a flight it’s the airport. So a gap after arriving at Lyon Part-Dieu is no longer measured from Lyon’s airport, about 18 km away. A gap between two rail-only places, such as Paris and Milan, now gets an estimate where before it had none.
- Your weights steer hub discovery. When a request gives a price/time weighting, the same weighting now picks which hub cities are searched, not only which trip ranks first. A traveller who values time is offered time-picked hubs. Weights of (0, 1) rank by journey time alone.
- Fuller trip ranking. Each half of a stopover trip is now composed from its full set of best routes, up to a safety bound of 200, rather than from 20 evenly spread samples. A route that was dropped before can now reach your top five.
- Wider stopover window. The onward half of a stopover trip is now searched on the dwell date and on each of the two days after it. A first half that arrives up to two days late, for example across the date line, still pairs with an onward route.
Behaviour changes
- Mixed-currency trips are no longer ranked. A trip whose parts are priced in different currencies, such as a GBP flight plus a EUR estimate, used to be ranked on a sum that mixed the two currencies. It now comes back in
incomplete_trips, unranked, with null totals, the same way as a trip that has a part with no price. It is never dropped.
- Lower-case place codes work everywhere.
par and PAR are the same place, from the cache through to the search. Trips come back with upper-case codes.
- Single-route rankings can differ. This only applies when a route has more than 20 non-dominated options. Those are no longer thinned in two steps, so the top five can change. Today’s searches stay well below that.
Price cache
- Old entries are retired automatically. Cache keys now carry the version of the cached data format. When that format changes, old entries are dropped at deploy instead of failing to load.
- Accounts are kept apart. Keys include a one-way fingerprint of the provider credentials, so a sandbox account’s fares are never served as a live account’s.
- Place codes are normalised. Keys and provider requests use a single canonical spelling of each place code, and the key parts are escaped so they can’t collide.
- A Redis outage no longer fails the search. If Redis can’t be reached, or hangs past a one-second timeout, Airbeat calls the provider directly and logs a warning. The search still answers.
Upgrade notes
- Every cached entry misses once after deploy, because the cache keys changed.
- For code that embeds Airbeat, two required fields were added:
- provider client sets gain a required
timetable;
- every offer client must expose
cache_scope. Use an empty string for clients without credentials.
- Wrong Redis password. Redis authentication errors are treated as an outage. A wrong password therefore degrades to uncached searches, logged at WARNING, instead of failing the search.
Known limitations
- No live rail timetable yet. The SNCF/DB timetable client is still a placeholder (#83), so production does not produce rail legs yet. Rail estimation is complete and tested, and is waiting on a timetable source.
- A hung Redis slows searches. Each cache call still waits up to its one-second timeout, so a search that makes many provider calls can be noticeably slower until Redis recovers.
Included work
Rail station coordinates (#16) · estimated rail legs from a timetable (#21) · cache key schema version (#85) · credential-scoped cache keys (#86) · normalised cache keys (#87) · neutral geometry module (#88) · cache outage falls through (#89) · gap ends measured by mode (#90) · full route frontiers (#91) · wider onward stopover window (#92) · weighted hub discovery (#93) · mixed-currency trips unranked (#94). Merged as one release in PR #105, followed by a whole-release review.
Tests at release: 230 passed. The 3 tests that need a live Redis were skipped in CI-less local runs.