Ratings that mean something to you
A restaurant's average rating tells you what everyone thinks. We built a second number that predicts what you will think, and learned when to stay quiet instead.
Every food app shows a star rating next to each restaurant. It is useful, but it answers a slightly odd question: what did everyone else think, on average? If you never eat spicy food and a restaurant is loved for its fiery chilli chicken, its 4.6 says very little about whether you will like it.
So Laayo shows two numbers. The overall rating is what everyone thinks. The “for you” rating is our prediction of how you, specifically, would rate that place. This post explains how we compute the second one, the bug that made the first version useless, and why sometimes the right prediction is no prediction.
The bug that showed the same number twice#
Our first personal rating was calculated inside the app. It looked at the first page of your order history and, when it did not have enough to go on, it simply copied the overall rating. In practice, almost everyone saw two identical numbers side by side: 4.7 overall, 4.7 for you. That is worse than showing nothing, because it tells people the feature does not work.
We moved the whole calculation to the server, where it can see your full history and everybody else’s, and we made one strict rule: if we do not have a real prediction, we do not show a personal number. The app shows a short “still learning your taste” state instead.
Getting the overall number right first#
Even the overall rating needed care. A restaurant with two five star reviews is not better than one with two hundred reviews averaging 4.6. We use a shrunk average: each restaurant’s rating is pulled towards the average for all restaurants, and the pull gets weaker as reviews accumulate. New places start near the middle and earn their way up or down.
What we know about your taste#
The personal rating is a blend of signals, each with a weight:
- Your own reviews, the strongest signal we have. A low star review of a restaurant counts for a lot.
- What you order, including which dishes and cuisines, from your full delivered order history.
- How often you go back. Reordering from the same place is a quiet but strong vote.
- Favourites and recommendations, including recommendations from people you follow.
- People like you, customers whose orders overlap with yours, and how they rated places you have not tried.
- Hard rules, such as veg mode. A place that does not suit a vegetarian customer should never be rated highly for them, no matter what else says.
- Fit, such as whether the restaurant’s prices match what you usually spend.
Each signal also adds to a confidence score. When confidence is low, the personal rating stays close to the overall one, or is not shown at all.
Learning hidden tastes#
Hand written signals only go so far. People’s tastes have patterns that are hard to name, so we added a small machine learning model.
It is a matrix factorisation model, the same family of techniques behind many recommendation systems. Each customer and each restaurant is represented by a short list of numbers, sixteen of them, learned from past behaviour. The model learns those numbers so that combining a customer’s list with a restaurant’s list predicts how that customer would rate that restaurant.
The training data is everything that hints at an opinion. A review is the clearest signal, weighted more heavily when it is recent. A delivered order is a mild positive. A cancelled order is a mild negative. Saving a restaurant as a favourite is a strong positive. The model trains every night in a few seconds, and its error on held back ratings is small enough to be useful.
When this model has seen enough of both you and a restaurant, its prediction gets a larger share of the blend. When it has seen little, it barely counts.
Matching the vibe#
A brand new restaurant has no history, so collaborative signals cannot help. For those we use content. We describe each restaurant by its name, cuisines and most ordered dishes, and we describe each customer by the dishes and cuisines they order most. A text embedding model turns both descriptions into vectors, and we compare them. A close match nudges the personal rating up.
Embeddings are refreshed weekly, and only for restaurants and customers whose description actually changed, so the cost stays tiny.
Explaining the number#
A prediction you cannot question feels like a trick. So every personal rating comes with short reasons, shown when you tap it: “You often order from places like this”, “Vibes match your usual go-tos”, or “Our model predicts this may not be your style”. The rating sheet also shows how far the personal number is above or below the overall one, so you can see at a glance whether a place is likely to suit you better or worse than average.
The guest bug#
One more bug is worth sharing because it is so easy to make. The personal rating endpoint serves both guests and signed in customers, so it does not require a login. Because of how authentication was configured, the code that looked up the current customer always found nobody on that endpoint. Every signed in customer was being treated as a guest, and getting the cold start answer. The fix was one line: ask explicitly for the app’s login token. The lesson was broader: when a feature quietly falls back to a safe default, test that it does not fall back all the time.
What’s next#
In the next app update, the rating chip on each restaurant becomes a small switch between “For you” and “Overall”, so you can flip between the two without opening a sheet. Later, we would like the same taste model to help order restaurant lists, so the places most likely to suit you come first.