· 5 min read · product · transparency · ranking

How we pick which deals to show you

A look inside the Fareduck scoring model — what 'a deal' actually means, why we use both heuristic and AI ranking, and how we keep our recommendations honest.

By Fareduck

If you've ever wondered "wait, why did Fareduck pick this fare over the other one?" — this post is the answer.

We score every flight offer we find through a deliberately two-stage process: a deterministic heuristic ranker first, then an optional LLM-based ranker on the survivors. Both are completely walled off from anything monetization-related. Here's exactly what each step does.

What "a deal" actually means at Fareduck

A deal isn't the cheapest fare. A deal is a fare that scores well on all four of these at once:

  1. Price — meaningfully below typical for the route, normalized for season and dates.
  2. Routing quality — direct beats connecting; reasonable layovers beat 23-hour torture connections.
  3. Persona fit — matches what you said you like (and avoids what you said you don't).
  4. Trust — a recognised carrier on a real route, not a fly-by-night charter doing one segment a week.

A flight at $239 from Denver to Lisbon with a 14-hour Frankfurt layover on an obscure carrier isn't a deal. It's a flight you'd cancel after booking. We don't surface that.

Stage 1: the heuristic ranker

Every offer that comes back from our flight-data provider runs through scoreOffers() — a pure deterministic function that produces a number from 0 to 100. No randomness, no machine learning. Same offer → same score, every time.

The factors it weights, ranked by influence:

Factor Weight Why
Price vs typical for route High The whole point
Direct vs 1-stop vs 2-stop High Routing quality matters as much as price
Loyalty carrier match High Status is worth real money to people who have it
Preferred-airline match Medium Softer signal than loyalty
Reasonable total journey time Medium We penalize anything over 24 hours
Excluded-airline match Hard exclude We never surface what you said no to

We deliberately don't weight things like:

The heuristic ranker's job is to throw out the obvious junk and keep the top ~15 candidates per user.

Stage 2: the LLM ranker (when we can afford it)

Heuristics don't catch nuance. "This person said they like quiet walking cities and we just found three options to Lisbon, three to Naples, and three to Bangkok — which 7 should we feature?" That's a question a heuristic can't reason about.

For the top candidates, we hand the list to Gemini 2.5 Flash with the user's full persona summary as context. Gemini reorders the list and writes a one-sentence rationale per pick — the "why we picked this" line you see on your digest.

We use the LLM judiciously:

The architectural wall

This is the part I'm most proud of and the part most easily overlooked.

Inside our code, the ranking module (packages/domain/ranking/) is structurally forbidden from importing anything from the affiliate, ad, or flight-provider modules. An ESLint rule enforces it. If a future engineer tries to add "boost deals from partners that pay us more," the build fails.

Same wall in the other direction: the affiliate module produces booking links after the ranking is done. It doesn't see scores. It can't bid for placement.

We do this because there's no other way to make trust real. You can't tell whether a recommendation is honest from a privacy policy paragraph. You can tell by the architecture, and you can read our code.

What this means in practice

When you open your Sunday digest:

If we can't find a partner-link match, we fall back to a Google Flights deep-link. No commission. We still show the deal.

When this breaks down

It's worth being honest about edge cases:

I'll write a follow-up post when we add real-time price re-verification at click time (post-Duffel-live approval).

— The Fareduck team